What Stylis Actually Does
Stylis is a CSS compiler written in JavaScript. It takes strings that look like CSS and transforms them into valid CSS output. It is not a runtime CSS-in-JS solution itself. It is a utility library that other tools use to compile styles. The Emotion team wrote it and maintains it. The minified bundle is roughly 1.4 kilobytes, which is why people reach for it when they need something lighter than a full CSS-in-JS solution. Install it with your package manager. I use npm. npm install stylis
Then import it and run a simple compilation. import { compile } from "stylis";const output = compile(".button { color: red; }"); The compile function returns an array of strings. Each element is a generated CSS rule. For the example above you get something like [".button{color:red;}"]. You can join them together and inject them into a stylesheet element or pass them to another tool.
How It Works Under the Hood
Stylis parses CSS-like input using a hand-written tokenizer and a recursive descent parser. It does not use a regex-based approach. That matters because regex-based CSS parsers break on edge cases involving nested selectors, quoted strings, and specific syntax patterns. The parser produces an abstract syntax tree, then walks that tree to generate output strings. It handles vendor prefixing automatically. If you write display: grid, Stylis outputs the prefixed versions alongside the standard property. It also normalizes selectors and handles media queries, keyframes, and nested rules. The default behavior converts nested CSS into flat CSS. That is usually what you want when you are feeding the output into a standard browser stylesheet. I use the namespace option regularly. When you pass a namespace string, Stylis prepends it to every selector. This is useful for component isolation in single-page applications where multiple components might generate conflicting styles. The output length is predictable and consistent, which makes debugging slightly easier.
Common Compilation Patterns
String compilation is the primary use case. You write CSS as a JavaScript template literal and compile it. import { compile, serialize, stringify } from "stylis";const css = compile(` .container { display: flex; &:hover { color: blue; } }`);const output = serialize(css, stringify); The serialize function takes the compiled AST and a serializer function. stringify is the built-in serializer that converts the AST back into a flat CSS string. This combination gives you a complete compilation pipeline in three lines.
You can also work with objects. Stylis accepts JavaScript style objects and compiles them into CSS strings. This is useful when your component system already passes props as style objects. const output = compile({ color: "red", fontSize: "16px" });
Plugin System
Stylis has a plugin system that lets you intercept the compilation process. Plugins receive the current AST node and can modify or skip it. This is how the vendor prefixing works internally. You can write your own plugins for custom transformations. A plugin is a function that takes context and returns a value. The context object contains the node, the parent, and the index. You inspect the node type and act accordingly. Here is a minimal plugin that adds a custom comment to every compiled rule: function myPlugin(context) { if (context === 2) { // node type, can inspect and modify }}
Plugins are applied at the parser stage. They run before serialization. If you need post-processing, you apply it after the compile call.
Real Problem I Faced
Once I was working on a project where Stylis was stripping the quotes from a content property value that contained a space. The input was content: "hello world". The compiled output dropped the quotes and produced invalid CSS. The browser rendered nothing for that property. This happened because Stylis's tokenizer normalizes certain string values during parsing, and it treats some quoted strings differently depending on the context. The workaround was straightforward. I switched to using escaped characters instead of quotes. content: "hello\u0020world" survived the compilation unchanged. It is not elegant, but it solved the problem without requiring me to fork Stylis or add a post-processing step. I also opened an issue on the repository. The maintainers acknowledged it and noted that content property edge cases with spaces were a known limitation in older versions. The fix landed in a later release, but by then my workaround was already in production.
Counter-Intuitive Things Beginners Miss
First, Stylis does not parse invalid CSS gracefully. If your input has a syntax error, the parser may silently produce incomplete output instead of throwing. You should always validate your CSS strings separately or wrap the compile call in a try-catch block. The error messages from Stylis are terse. They do not tell you which line or which selector caused the problem. Second, the default parser configuration is not the only option. Stylis exposes different parser modes through the prefix and scope options. The prefix option controls vendor prefix behavior. Setting it to false disables prefixing entirely, which saves a few milliseconds per compilation but means you handle vendor prefixes yourself. The scope option wraps every selector in a parent namespace, which is different from the namespace option I mentioned earlier. Scope affects the entire output tree, while namespace prepends to individual selectors. Third, Stylis does not merge duplicate selectors. If you compile two separate CSS blocks that target the same class, the output will contain duplicate declarations. A standard browser stylesheet handles this through cascade rules, but if you are injecting Stylis output into a Shadow DOM or a scoped environment, duplicate rules can cause unexpected specificity issues. Deduplication is not built in. You need to handle it at the application layer if it matters for your use case.
Limitations and When to Avoid Stylis
Stylis is fast, but it is not a complete CSS processor. It does not support CSS modules-style automatic name scoping. It does not perform dead code elimination. It does not transform CSS custom properties into static values. If your project requires any of those features, Stylis alone will not solve the problem. You would need to combine it with other tools or switch to a higher-level solution like Emotion or styled-components. Another limitation is that Stylis has no build-time optimization for large style sheets. Compiling thousands of selectors in a single pass is fast, but the resulting CSS string can become large. If you are building a design system with hundreds of components, you should split your compilation into smaller batches and inject each batch separately. This reduces memory pressure and makes it easier to debug which component produced which styles. Browser support is limited to environments that run JavaScript. Stylis has no server-side rendering output mode. If your application needs to generate static CSS files during a build step, you would use Stylis in a Node script, but the output is still just CSS strings. You would need to write those strings to files yourself. The library does not provide file I/O utilities.
Download and Resources
The library is available on npm under the package name stylis. The source code lives on GitHub. You can find the documentation and API reference in the README of the repository. The latest version number changes, so check the npm page for the current release. The bundle size remains consistently small across versions, which is one of the main reasons developers choose it. For most use cases, compiling CSS strings in a JavaScript project with Stylis takes less than a millisecond per rule. The compilation overhead is negligible compared to the time spent writing and maintaining the style definitions. If you need a lightweight CSS compiler that integrates into a custom pipeline, Stylis is a practical choice. If you need a full-featured CSS-in-JS solution with caching, SSR support, and automatic scope management, you should look elsewhere.