Distillate vs CSS-in-JS
Distillate is a component library authoring tool. Emotion, CSS Modules, Tailwind, and vanilla-extract are primarily application tools. The audiences overlap, but the core problems differ.
Use Distillate when you need both a public stylesheet and compact self-contained artifact payloads from the same authored source — and the library must carry no design opinions of its own.
Use Emotion (or similar) when you are styling an application directly and have no need to export tree-shaken CSS alongside HTML.
Emotion generates class names at runtime by hashing template content and injecting <style> elements. Distillate records declarations once at module-construction time and emits CSS on demand through a collector.
| Emotion | Distillate | |
|---|---|---|
| Primary audience | Application developers | Component library authors |
| Style authoring | css\...`` at render time |
createStyleModule once at module load |
| Runtime variation | New template → new class | CSS custom properties (vars, theme tokens) |
| Output modes | DOM injection | Stylesheet or compact artifact |
| Tree-shaking | No — all styles inject eagerly | Artifact collector drops unreached entries |
| Typed tokens | No | lightDark, cq, vars |
| Object styles | css({ color: 'red' }) |
Not supported |
| Keyframes | keyframes\...`` |
Not supported |
styled.* |
Yes (@emotion/styled) |
No |
| Bundle constraint | Works in any bundle | One copy per runtime required |
The @elastic/distillate/emotion entry point is a drop-in surface (css / cx / injectGlobal) over the same registry. It lets you keep existing call sites while gaining the collector and artifact-export path. See Migrate from Emotion.
- You ship a component library that exports both a loadable stylesheet (for apps) and self-contained HTML payloads (emails, AI reply cards, SVG renders, exported reports).
- You want typed design-token references (
tokens.colors.ink) with pruning — unused tokens drop out of artifact CSS. - You need the same styles in two name modes: human-readable for development, short-identifier compact when HTML and CSS travel together.
- You want to assert at test time that every
var(--...)in emitted CSS has a matching declaration (assertVarRefsHaveDeclarations).
- You need per-render dynamic class names. Modules are static after construction. Put variation on CSS custom properties, not new templates per render.
- You are building an application, not a library. Distillate's artifact-export path has no value when styles never leave the host app.
- You rely on
styled.*, keyframes,@supports, or object styles. These are unsupported. - Your bundle cannot guarantee a single copy at runtime. See Single-copy invariant. For plugin-host architectures (Kibana-style), the platform must expose Distillate as a shared dependency.
CSS Modules scope class names per file with a bundler plugin. They do not tree-shake unused declarations, have no typed token system, and cannot emit compact payloads. Choose CSS Modules for simple scoped styles in an application; choose Distillate for a library that needs typed tokens and artifact export.
Static extraction tools run at build time and produce real CSS files with zero runtime cost. The tradeoff: they cannot follow an actual render to determine which handles were reached. Distillate's artifact collection is a runtime side effect of resolving class names, which is the correct model when the emitted CSS must exactly match the rendered HTML tree.
Vanilla-extract offers typed token support similar to createThemeContract. Choose vanilla-extract if build-time extraction is a hard requirement or if runtime is forbidden (edge/serverless cold-start sensitive). Choose Distillate if artifact export or runtime reachability collection is required.
Tailwind generates utility classes from a predefined scale. Distillate emits authored property blocks from tagged templates. They are not competing tools — a library can use Distillate internally while a host application uses Tailwind. The key difference: Tailwind cannot produce a compact, tree-shaken CSS payload tied to a specific render tree.