tw is an OCaml implementation of Tailwind CSS v4.
You assemble utilities as ordinary OCaml values and compile them to CSS through
the same typed variable-and-layer pipeline Tailwind uses, producing
byte-for-byte identical output. Unknown utilities are caught at compile time,
and nothing depends on Node.js, PostCSS, or the Tailwind CLI at run time.
open Tw
(* A set of utilities is a value of type [Tw.t list]. *)
let card =
[
flex; flex_col; gap 4; p 6;
bg white; rounded_lg; shadow_sm;
border; border_color ~shade:200 gray;
hover [ shadow_md ];
dark [ bg ~shade:800 gray; text ~shade:100 gray ];
]- Tailwind v4 parity — output is checked byte-for-byte against the real Tailwind CSS CLI across the upstream utility and variant test suites.
- Both official plugins —
@tailwindcss/typography(prose) and@tailwindcss/formsare fully implemented. - Type-safe — utilities are typed constructors, so invalid combinations are compile errors rather than silent no-ops.
- String parsing —
Tw.str "flex p-4 bg-blue-500"turns class strings into typed utilities for dynamic class lists. - Pure OCaml — no JavaScript toolchain required; the library also compiles to JavaScript with js_of_ocaml.
Requires OCaml >= 5.2.
opam install twopen Tw
(* Spacing scales: the integer is multiplied by 0.25rem, so [p 4] is 1rem. *)
let layout = [ flex; items_center; gap 4; p 6; mx_auto; max_w_4xl ]
(* Colors default to shade 500; [~shade] picks another, [~opacity] adds alpha. *)
let colors = [ bg blue; text white; border_color ~shade:300 gray; bg ~opacity:50 white ]
(* Variants nest as functions: responsive breakpoints, state, and dark mode. *)
let responsive =
[ p 4; md [ p 8 ]; lg [ p 12 ]; bg blue; hover [ bg ~shade:600 blue ]; dark [ text ~shade:300 gray ] ]
(* The typography plugin, including per-element variants. *)
let article =
[ prose; prose_lg; mx_auto; prose_headings [ text ~shade:600 blue ]; prose_code [ bg ~shade:100 gray ] ]open Tw
(* [to_classes] returns the value for an HTML [class] attribute. *)
let class_attr = to_classes [ flex; p 4; bg blue ]
(* [to_css] builds a stylesheet; [Css.to_string] renders it to text. *)
let stylesheet = to_css [ flex; p 4; bg blue; hover [ bg ~shade:600 blue ] ]
let css = Css.to_string ~minify:true stylesheet(* [Tw.str] parses a class string into utilities (raises on unknown classes). *)
let styles = Tw.str "flex items-center gap-4 p-6 bg-white rounded-lg shadow-sm"
(* [Tw.of_string] parses a single class and returns a result. *)
let parsed = Tw.of_string "hover:bg-blue-600" (* Ok _ *)
let rejected = Tw.of_string "not-a-class" (* Error (`Msg _) *)# Generate CSS for specific classes
tw -s "flex p-4 bg-blue-500 hover:bg-blue-600"
# Scan files for classes
tw src/ > styles.css
# Compare with real Tailwind CSS
tw -s "prose mb-4 dark:text-gray-300" --diffAll Tailwind CSS v4 core utilities and both official plugins:
| Category | Examples |
|---|---|
| Layout | flex, grid, block, hidden, container |
| Spacing | p-4, mx-auto, gap-4, space-x-2.5 |
| Sizing | w-full, h-screen, size-4, max-w-4xl |
| Typography | text-sm, font-bold, leading-tight, tracking-wide |
| Colors | bg-blue-500, text-white, border-gray-300 |
| Opacity | bg-blue-500/50, text-white/80 |
| Borders | border, rounded-lg, divide-x, ring-2 |
| Effects | shadow-sm, opacity-50, blur-lg |
| Transforms | scale-50, rotate-45, translate-x-1/2 |
| Transitions | transition-colors, duration-200, ease-in |
| Interactivity | cursor-pointer, select-none, scroll-smooth |
| Filters | backdrop-blur, grayscale, invert |
| Tables | table-auto, border-collapse, border-spacing-4 |
| Prose | prose, prose-lg, prose-sm, prose-headings:, prose-pre: |
| Forms | form-input, form-select, form-checkbox |
| Variant | Example |
|---|---|
| Responsive | sm:, md:, lg:, xl:, 2xl: |
| State | hover:, focus:, active:, disabled: |
| Dark mode | dark: |
| Group/Peer | group-hover:, peer-checked: |
| Pseudo-elements | before:, after:, marker:, placeholder: |
| Prose elements | prose-headings:, prose-p:, prose-a:, prose-code: |
| Stacked | dark:hover:, md:focus:, sm:dark:hover: |
The --diff and --tailwind CLI modes require the tailwindcss standalone
binary for comparison testing:
# macOS (Apple Silicon)
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64
chmod +x tailwindcss-macos-arm64
sudo mv tailwindcss-macos-arm64 /usr/local/bin/tailwindcss
# Linux (x64)
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64
chmod +x tailwindcss-linux-x64
sudo mv tailwindcss-linux-x64 /usr/local/bin/tailwindcssdune build # build the library and CLI
dune runtest # run all tests, including README examples
# Compare a single utility with Tailwind
dune exec -- tw -s "bg-blue-500 hover:bg-blue-600" --diffISC — see LICENSE.md.