A Tailwind CSS v4-compatible compiler written in Zig. Accepts a list of CSS class candidate strings and returns minified production CSS. Everything happens in memory — no filesystem scanning, no external processes, no CLI.
Designed as the CSS compilation engine for Beacon CMS, callable from Elixir as a NIF via Zigler.
Benchmarked against the official Tailwind CLI v4.2.2 — 10 rounds, each with 100–150 freshly generated HTML pages and ~3,000 unique Tailwind classes (Apple M4):
| Metric | Zig Compiler | Tailwind CLI v4 | Difference |
|---|---|---|---|
| Avg compile time | 1.64 ms | 178 ms | 108x faster |
| Range | 1.56 – 1.77 ms | 170 – 200 ms | |
| Peak memory | 4.4 MB | 131 MB | 30x less |
Zero output differences — every candidate produces byte-identical CSS to the official Tailwind CLI.
Add to your mix.exs:
def deps do
[{:tailwind_compiler, "~> 0.0.1"}]
endPrecompiled NIF binaries are available for x86_64-linux, aarch64-linux, aarch64-macos, and x86_64-macos. The correct binary is downloaded automatically during mix compile.
To force compilation from source (requires Zig 0.15.2+ and the zigler dependency):
# Add zigler to your deps
{:zigler, "~> 0.15.1", runtime: false}TAILWIND_COMPILER_PATH=true mix compileTailwindCompiler.compile(["flex", "p-4", "hover:bg-blue-500/50", "sm:text-lg"])
#=> {:ok, ".flex{display:flex}.p-4{padding:calc(var(--spacing)*4)}..."}
# Without preflight (base CSS reset)
TailwindCompiler.compile(["flex", "hidden"], preflight: false)
# With theme overrides (custom colors, spacing, fonts)
TailwindCompiler.compile(["text-brand", "p-4"],
theme: ~s({"colors":{"brand":"#3f3cbb"},"spacing":"0.5rem"}))
# With custom CSS (plugins, user stylesheets)
TailwindCompiler.compile(["flex"],
custom_css: ".custom-btn{background:blue;padding:1rem}")
# Bang variant (raises on error)
css = TailwindCompiler.compile!(["flex", "p-4"])The NIF runs on a dirty CPU scheduler. For a typical site (~3,000 candidates), expect ~1.5ms latency.
const tailwind = @import("tailwind_compiler");
const candidates = [_][]const u8{ "flex", "p-4", "hover:bg-blue-500/50", "sm:text-lg" };
const css = try tailwind.compile(allocator, &candidates, null, false, null);pub fn compile(
alloc: std.mem.Allocator,
candidates: []const []const u8, // Tailwind class names
theme_json: ?[]const u8, // Optional JSON theme overrides
include_preflight: bool, // Include base CSS reset
custom_css: ?[]const u8, // Optional raw CSS to append (plugins, user stylesheets)
) ![]const u8Requires Zig 0.15.2 and Elixir 1.17+.
# Elixir
mix deps.get
mix compile
mix test
# Zig standalone
zig build test
zig build run
zig build -Doptimize=ReleaseFastDisplay, position, visibility, isolation, box-sizing, float, clear, overflow, overscroll, object-fit, pointer-events, resize, user-select, touch-action (composable), cursor, appearance, flex direction/wrap/grow/shrink, grid flow, justify/align/place content/items/self (including safe alignment), text alignment/decoration/transform/overflow/wrap, whitespace, word-break, hyphens, font style/variant/smoothing (composable), list style, vertical-align, background attachment/clip/origin/repeat/size/position, border style/collapse, outline, mix/bg blend mode, table layout, caption side, transitions, will-change, contain, forced-color-adjust, sr-only, field-sizing, scroll behavior/snap, break-after/before/inside, box-decoration, content-visibility, color-scheme, font-stretch, transform-style, backface-visibility, mask-clip/origin/mode/composite/type/repeat/size/position (with -webkit- prefixes), and more.
- Spacing:
p-*,m-*,gap-*,inset-*,top/right/bottom/left-*,scroll-m*,scroll-p*,basis-*,mbs-*,mbe-*,pbs-*,pbe-*,mis-*,mie-*(logical properties) - Sizing:
w-*,h-*,min-w/h-*,max-w/h-*,size-*,inline-*,block-*,min-inline/block-*,max-inline/block-*+ viewport units (svw,lvw,dvw,svh,lvh,dvh,lh) - Colors:
bg-*,text-*,border-*,accent-*,caret-*,fill-*,stroke-*,outline-color-*,decoration-*,shadow-color-*,divide-*,placeholder-*— all with opacity modifier support (bg-red-500/50pre-resolved to#hex) - Typography:
text-sm/lg/xl(font-size + line-height),font-sans/bold(family + weight),leading-*,tracking-*,font-weight-* - Borders:
border-*(width + color),border-x/y/s/e/t/r/b/l-*,rounded-*(all corners),divide-x/y-* - Effects:
shadow-*,inset-shadow-*,text-shadow-*,ring-*,inset-ring-*,ring-offset-*,opacity-*— composablebox-shadowsystem - Filters:
blur-*,brightness-*,contrast-*,grayscale,hue-rotate-*,invert,saturate-*,sepia+ allbackdrop-*— composablefilter/backdrop-filter - Transforms:
rotate-*,scale-*,translate-x/y/z-*,skew-x/y-*,rotate-x/y/z-*,scale-z-*— composable custom properties (2D + 3D) - Grid:
cols-*/grid-cols-*,rows-*/grid-rows-*,col-span-*,col-start/end-*,row-span-*,row-start/end-*,auto-cols/rows-* - Gradients:
bg-linear-to-*/bg-gradient-to-*,bg-radial-*,bg-conic-*,from-*,via-*,to-*— composable stops - Layout:
aspect-*,columns-*,perspective-*,origin-*,container(responsive max-widths) - Transitions:
duration-*,delay-*,ease-*,animate-* - Misc:
z-*,order-*,line-clamp-*,content-*,list-*,outline-offset-*,underline-offset-*,grow-*,shrink-*,mask-image-*,border-spacing-*
- Pseudo-classes:
hover(with@media(hover:hover)),focus,focus-visible,focus-within,active,visited,target,first,last,only,odd,even,disabled,enabled,checked,required,valid,invalid,placeholder-shown,autofill,read-only,open,inert, and more - Pseudo-elements:
before,after(withcontentinjection),marker,selection,placeholder,file,backdrop,first-letter,first-line - Media:
dark,print,motion-safe/reduce,contrast-more/less,portrait,landscape,forced-colors,inverted-colors,pointer-*,noscript - Responsive:
sm,md,lg,xl,2xl,max-*,min-* - Container:
@sm,@md,@lg,@xl,@min-*,@max-* - Compound:
group-*,peer-*,has-*,not-*,in-*(includinggroup-aria-*,group-data-*) - Functional:
aria-*,data-*,supports-*,nth-*,nth-last-*,nth-of-type-*,nth-last-of-type-* - Other:
ltr,rtl,starting,*,**, arbitrary[&>svg]
- Composable
@propertydeclarations for shadow, ring, translate, scale, gradient, filter, backdrop-filter, font-variant-numeric, border-spacing @keyframesfor built-in animations (spin, ping, pulse, bounce)@layer themewith tree-shaken CSS variables (only emits what's used)@layer basewith Tailwind v4 preflight- CSS.escape() spec-compliant selector escaping
- Pre-computed oklch→sRGB color conversion (288-color lookup table from LightningCSS)
- Arena allocator — one bulk deallocation per compile call
- Arbitrary values:
bg-[#0088cc],w-[calc(100%-2rem)],[color:red] - Theme function shorthand:
bg-(--my-color)→var(--my-color) - Negative utilities:
-mt-4,-rotate-12,-translate-y-2 - Important modifier:
flex!→!important - Fraction values:
w-1/2→50% - Custom CSS passthrough: append plugin CSS, user stylesheets, or custom components
- JSON theme overrides: custom colors, spacing, fonts merged with defaults
# Run 10-round benchmark (default)
mix run benchmark/benchmark.exs
# Custom rounds
BENCH_ROUNDS=20 mix run benchmark/benchmark.exs
# Include preflight CSS
BENCH_PREFLIGHT=1 mix run benchmark/benchmark.exslib/
tailwind_compiler.ex Elixir public API
tailwind_compiler/nif.ex Zigler NIF wrapper (dirty CPU scheduler)
src/
root.zig Zig public API: compile()
compiler.zig Context, dedup, sort, container responsive, @property/@keyframes
candidate.zig Bracket-aware parser, findRoots, modifiers, arbitrary values
utilities.zig ~350 static + ~85 functional utility handlers
variants.zig 77+ variant definitions, ordering, and application
emitter.zig CSS.escape(), minified output, @layer/@property/@keyframes
color.zig oklch→sRGB conversion, hex8 formatting, 288-color lookup
theme.zig JSON parsing, variable resolution, usage tracking
default_theme.zig Complete Tailwind v4 default theme (colors, spacing, etc.)
test/
tailwind_compiler_test.exs 12 ExUnit tests
benchmark/
run_benchmark.sh 10-round benchmark runner
generate_pages.py Parametric HTML page generator
bench_zig.zig Zig benchmark binary with μs-precision timing
results/report.html Interactive results dashboard
MIT