Imagine you need to integrate AI into your web app. Let's say you want to quickly translate text. Preferably, you'd like to use something free and open source (you don't want any surprise bills at the end of the month). One more thing: people use your app while traveling, so it must continue working when they have poor connections or no connection at all.
How many lines of code do you think that would take?
If your answer is "3," you're right. Take a look:
import { pipeline } from '@huggingface/transformers'
const t = await pipeline('translation', 'Xenova/opus-mt-en-es', { device: 'webgpu' })
await t('Hello, world!') // [{ translation_text: '¡Hola, mundo!' }]
That's Transformers.js, a library that lets you run AI models directly in the browser.
Wait... what? No API calls? No backend? Exactly. Here's what happens:
This offline-first strategy works especially well with small, task-specific models. For example, the model used in the snippet above, opus-mt-en-es, translates text from English to Spanish. It's about 115 MB: small compared to most local models, but still a real download the first time around, so keep that in mind.
And there you have it! Your AI-powered translation feature is ready.
But how does Transformers.js work under the hood? Which models are available? And what are the trade-offs compared to traditional AI providers?
In this article, we'll answer all of those questions. Let's dive in!
Transformers.js is a JavaScript library from Hugging Face that lets you run pre-trained AI models 100% locally in the browser. It started as a personal project by Joshua Lochner, who needed to run a spam classifier as a browser extension and found nothing that could do it. He took the "fine, I'll do it myself" approach and now the library logs over 1.7 million unique monthly users.
The library mirrors the Python transformers API, so the same task names, the same pipeline abstraction, and the same model IDs from the Hugging Face Hub work in JavaScript. At the time of writing, it supports 27 tasks across four categories: text, vision, audio, and multimodal. It covers 155 model architectures, with more than 1.8k models already converted and ready to use on the Hub.
Models that run in your browser, but also in Node, Bun, or Deno. Plus Web Workers, browser extensions, Electron apps, and serverless edge functions.
You might be wondering: "But... why? Isn't it easier and relatively cheap to integrate with an AI provider like OpenAI or Anthropic?"
It's true that connecting to a third-party API is easier than ever. However, running models directly in the browser has concrete advantages:
Transformers.js doesn't implement its own machine learning framework. Instead, it acts as a thin layer on top of two existing technologies.
optimum library can convert it for you.Transformers.js doesn't execute models itself. It downloads the model, prepares the inputs and outputs, and lets ONNX Runtime Web handle the heavy lifting.
Don't worry if this sounds complicated at first. As a developer, you don't need to deal with any of these details. You simply call pipeline(), and the library takes care of the rest.
Browser support has improved dramatically over the past few years.
Chrome and other Chromium-based browsers offer full WebGPU support, giving Transformers.js access to GPU acceleration. Firefox goes a step further by using Transformers.js as part of its built-in AI Runtime. And with Safari 26 adding WebGPU support across macOS, iOS, iPadOS, and visionOS, GPU-accelerated AI is now available across the entire Apple ecosystem.
In other words, browser-based AI is no longer limited to a few experimental browsers.
You can install the library using your favorite package manager:
npm install @huggingface/transformers
Then, import pipeline wherever you need it:
import { pipeline } from '@huggingface/transformers'
The pipeline() function is the main entry point to the library. It loads the requested model, downloading it if necessary, caches it using the browser's storage APIs, and returns a function you can use to run inference. We call it using await because loading the model is asynchronous. On the first run, this will wait for the model to download. After that, the cached files are reused, from the browser's Cache API or IndexedDB, making startup much faster.
The first argument specifies the task you want to perform:
const classifier = await pipeline('sentiment-analysis')
Transformers.js supports a wide range of tasks, including:
Most of the time, selecting the task is all you need. If you don't specify a model, Transformers.js automatically downloads a sensible default from the Hugging Face Hub.
However, as we did in our initial snippet, you can explicitly choose which model to use by passing its name as the second argument:
const classifier = await pipeline(
'sentiment-analysis',
'Xenova/distilbert-base-uncased-finetuned-sst-2-english'
)
Model names follow the same format as their Hugging Face Hub repositories: <author>/<model>. In this case, we're loading a DistilBERT model fine-tuned for sentiment analysis.
The Hugging Face Hub contains thousands of compatible models. Some are tiny and optimized for running in the browser, while others are larger and offer better accuracy at the cost of longer download times and higher memory usage.
Choosing the right model is usually a trade-off between size, speed, and quality. For browser applications, smaller, task-specific models tend to provide the best user experience.
Now that the model is loaded, running inference is as simple as calling the function with your input:
const result = await classifier('I love Transformers.js!')
// [{ label: 'POSITIVE', score: 0.9998... }]
Here's what's happening:
classifier is just an async function returned by pipeline().From your perspective, it's just a function call. Under the hood, Transformers.js handles preprocessing, inference, and postprocessing for you.
The third pipeline argument is an optional configuration object. For example, with our sentiment analysis classifier:
const classifier = await pipeline(
'sentiment-analysis',
'Xenova/distilbert-base-uncased-finetuned-sst-2-english',
{
device: 'webgpu',
dtype: 'q8'
}
)
Here, we're making two changes:
device: 'webgpu' tells Transformers.js to use the GPU when available instead of running the model on the CPU. This can significantly improve performance for supported devices.dtype: 'q8' loads a quantized version of the model, which uses 8-bit numbers instead of the full precision version. The result is a smaller model that requires less memory and usually loads faster, with a small trade-off in accuracy.If you don't provide this third argument, Transformers.js automatically chooses sensible defaults based on the environment. However, for browser applications, options like WebGPU and quantized models can make a big difference, especially when running larger models on resource-constrained devices.
Alright! Let's build something practical. Usually, I find myself with a ChatGPT tab open to tidy up sentences. I know, overkill: I don't need a state-of-the-art model to fix grammar and syntax. Can I replace that with my own mini app, using Transformers.js? Totally!
Let's create a grammar checker that takes a sentence, runs it through a model in the browser, and returns a corrected version.
We need a model that understands text and can generate a corrected version.
Xenova/LaMini-Flan-T5-783M is a good fit. It's an instruction-following text-to-text model, which means we can hand it a plain-English instruction along with the text and it'll do the job.
We're going to build this as a single HTML file (no frameworks, no build step), so we can import Transformers.js straight from a CDN:
import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4';
const corrector = await pipeline(
'text2text-generation',
'Xenova/LaMini-Flan-T5-783M'
);
Since this is an instruction-following model, we don't need anything fancy. We just tell it what to do and give it the text:
const text = 'she dont know nothing about it';
const prompt = 'Fix all grammar, spelling, and punctuation errors in this text: ';
const output = await corrector(prompt + text, {
max_length: 200,
do_sample: false
});
const corrected = output[0].generated_text.trim();
// "She doesn't know anything about it."
Here's what's happening:
do_sample: false makes the output deterministic. For a grammar checker, we don't want creativity.In practice:
she dont know nothing about it
→ She doesn't know anything about it.
their going to there house
→ They're going to their house.
Yes, I've readed the document
→ Yes, I've read the document.
Pretty good! It fixes the double negative, the homophones, and the tense mistake, capitalizing and punctuating along the way.
However, there are a few trade-offs we should keep in mind.
It's not Grammarly
Our little app won't match Grammarly when it comes to nuance. It handles agreement errors, basic tense mistakes, homophones, and missing punctuation reliably. But it has limits:
me and him was going to the park
→ I and him were going to the park.
i has went to the store and buyed to much apple
→ I have gone to the store and bought too much apple.
In the first example, it fixes the verb (was → were) but mangles the pronouns (I and him should be He and I). In the second, it fixes the tenses but leaves too much apple (should be too many apples). And if you throw a sentence with several tangled errors at it, the output can drift or lose part of the meaning entirely. But for a quick grammar check before you hit send... it gets the job done.
Initial Download
This model is around 800 MB, so the first run requires a substantial download. It's cached afterward, but that initial download would require a fast connection.
Performance
By default, inference runs on the CPU, taking a couple of seconds per correction on a typical laptop. On devices with WebGPU, it's noticeably faster.
This minimalist version is just an HTML file with a textarea and a button.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Grammar Checker</title>
</head>
<body>
<h1>Grammar Checker</h1>
<textarea id="input" rows="5">she dont know nothing about it</textarea>
<button id="check">Check grammar</button>
<p id="status"></p>
<div id="output"></div>
</body>
</html>
Nice. Now we can add the logic in a script of type module:
<script type="module">
import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4';
const $ = id => document.querySelector(id);
const [input, button, status, output] = ['#input', '#check', '#status', '#output'].map($);
let corrector;
button.onclick = async () => {
button.disabled = true;
output.textContent = '';
corrector ??= await (async () => {
status.textContent = 'Loading model…';
return pipeline('text2text-generation', 'Xenova/LaMini-Flan-T5-783M');
})();
status.textContent = 'Checking…';
const [{ generated_text }] = await corrector(
`Fix all grammar, spelling, and punctuation errors in this text: ${input.value}`,
{ max_length: 200, do_sample: false }
);
output.textContent = generated_text.trim().replace(/^["']|["']$/g, '');
status.textContent = '';
button.disabled = false;
};
</script>
That's the whole app! A few things worth calling out:
corrector in a variable and only build it once.async and we await both the pipeline() call and the correction.Save it as grammar-checker.html and serve it with any static file server (browsers won't run ES module imports over file://):
npx serve
Our mini app is done! No backend, no build tooling, no API keys, no per-token bill. Not bad!
We started with a three-line translation snippet and ended with a working grammar checker running 100% in the browser, no conversation history traveling to a server and back. Just a small model, cached locally, doing one thing well.
If you want to learn more about Transformers.js and the incredible applications it enables, check out this presentation by its creator, Joshua Lochner: Transformers.js: State-of-the-Art Machine Learning for the Web.
We hope this article sparked some ideas! Think about the places where you can integrate AI directly into your users' browsers. If you build something with it, make sure to let us know.
Until next time!
We appreciate your interest.
We will get right back to you.