Detecting toxic comments in the browser
Online abuse remains a persistent problem for communities and platforms. One increasingly practical line of defense is running toxicity detection directly in the browser, before a comment ever reaches your server. This article walks through a working implementation using the toxic-bert model and Hugging Face's Transformers.js library, with a focus on the code decisions and UX trade-offs involved.
You can try the interactive demo and inspect the full source code, which runs in current versions of Safari, Chrome, Edge, and Firefox.
Choosing a model and library
Transformers.js provides the high-level APIs needed to execute machine learning models in the browser. The demo's classification pipeline is derived from the library's standard text classification example. The model used is Xenova/toxic-bert, a web-compatible port of the unitary/toxic-bert pretrained model, which is designed specifically to identify toxic language patterns. The model page details the output labels, including its approach to identity attacks.
Once the model is downloaded to the client, inference latency is low. In testing on a standard Pixel 7 over Chrome, classification took under 500 milliseconds. Because performance will vary by hardware and browser, benchmark against your own target user base before deployment.
Core implementation steps
Define your threshold
The classifier returns toxicity scores between 0 and 1. A practical threshold—commonly 0.9—lets you catch overtly toxic language while minimizing false positives on innocuous comments that happen to trigger partial matches.
Import the necessary components
Pull in the required pieces from @xenova/transformers, along with shared constants and configuration values like the threshold just described.
Load the model off the main thread
Instantiating a pipeline is the first step toward running inference. The minimal form is:
const classifier = await pipeline('text-classification', MODEL_NAME);
The pipeline function takes the task name ('text-classification') and the model name (Xenova/toxic-bert) as its two arguments. In Transformers.js, a pipeline is a higher-level API that abstracts the underlying model loading, tokenization, and post-processing.
The demo takes a slightly more involved path: model preparation is offloaded to a web worker so the UI thread never blocks while the model downloads and initializes. The worker communicates status back to the main thread via a set of message codes, each mapped to a phase in the model lifecycle or inference run.
Run classification on user input
A classify function calls the prepared classifier on the submitted text and returns the raw label/score pairs. In the demo, this function is invoked from the worker whenever the main thread sends new user input—specifically, after the user pauses typing for a fixed delay.
Evaluate the results
Each label's score is compared against the threshold. If any toxic label clears the bar, the comment is flagged as potentially toxic. The code makes the more detailed toxicity type list available to the main thread, even though the demo UI uses only the boolean result.
Show a hint without blocking
When the flag is set, the UI shows a warning. Two UX decisions are worth calling out:
- Posting is always allowed. The client-side detection is advisory. Users may post even if the model hasn't finished loading or if the comment was flagged as toxic. A server-side or human review system should serve as the authoritative second layer; some applications may choose to inform users that their post passed client checks but was later flagged upstream.
- The UI stays quiet on clean comments. The demo gives no "nice comment" feedback because the classifier occasionally misses toxic content. Positive reinforcement on a false negative would send the wrong signal.
Known limitations and alternatives
Limitations
The current model is largely English-only; multilingual deployment will require fine-tuning. Several Hugging Face toxicity models support other languages (including Russian and Dutch), but aren't yet compatible with Transformers.js. Even within English, toxic-bert handles overt toxicity well but may miss ironic, sarcastic, or culturally contextual cases. Deciding which terms—or emoji—should count as toxic is inherently subjective and may require fine-tuning to match your threat model.
Alternative tooling
Two other client-side options exist. MediaPipe offers a text classification pipeline, though you must confirm your chosen model is compatible with classification tasks. TensorFlow.js ships its own toxicity classifier with a smaller, faster-to-fetch model—but it hasn't received significant optimization recently, and inference may lag Transformers.js as a result.
A layered safety net
Client-side toxicity detection offers real-time feedback that can deter abuse while reducing the classification workload on your servers. The browser-based approach is functional, but plan around model serving costs and download size, and apply performance best practices such as caching the model after the first visit. For robust protection, pair client-side detection with server-side checks rather than relying on one layer alone.



