Skip to content
Transtyle v0.1.0-alpha.5
ok: build 10:17 UTC31 pages main · 7ed21c5

Alpha — breaking changes ship without a deprecation cycle. What is built vs. planned

shadcn/ui exporter

See it live

Acme · Cathode · GOV.UK · Carbon: one page, four design systems, compiled to shadcn/ui. All 44 demos →

The first reference exporter. shadcn’s theme is a set of semantic CSS variables, so it exercises the semantic catalog almost 1:1. That made it the right target to build first and the cleanest to learn from.

On the Acme example that’s 131 custom-property declarations across :root and .dark, 71 distinct variables, each one classified in report.json.

Era profiles#

shadcn isn’t a versioned library; its theming convention shifted with the Tailwind v3 → v4 transition. The exporter ships two profiles, keyed on your Tailwind version. Set "version": "3.4.17" on the target to select the tailwind-v3 profile; "4.1.13" selects tailwind-v4 (the ranges are >=3 <4 and >=4 <5; anything else fails with TST1313, see target versions). options.era still picks one explicitly (see target instances); when both are set and disagree, the era wins and the build warns with TST2105.

tailwind-v4 (default) tailwind-v3
Color syntax OKLCH values HSL channel triplets (221 83% 53%)
Artifacts globals.transtyle.css (:root + .dark + @theme inline) globals.transtyle.css (@layer base) + tailwind.theme.transtyle.cjs
Wiring @theme inline maps variables to Tailwind utilities Merge the .cjs snippet into tailwind.config (hsl(var(--x)) convention)
Fidelity note Lossless within sRGB OKLCH → HSL may gamut-clamp; clamped variables are classified approximated
"targets": {
  "shadcn":    { "options": { "era": "tailwind-v4" }, "output": "dist/shadcn" },
  "shadcn-v3": { "exporter": "shadcn", "options": { "era": "tailwind-v3" }, "output": "dist/shadcn-v3" }
}

What maps where#

The full 33-variable set is emitted; the highlights:

shadcn variable Comes from Note
--background / --foreground elevation.0.surface / text.base
--card / --popover elevation.1.surface / elevation.3.surface the role-grid elevation ladder: card is one step up from the page, popover another two; see the language reference
--primary + --primary-foreground primary.solid + primary.on-solid the foreground is contrast-picked and AA-checked
--secondary, --muted neutral.tint shadcn’s “secondary” is a subtle surface, not your brand secondary, a deliberate mapping decision
--accent + --accent-foreground accent.tint + accent.on-tint derived accent is brand-tinted, visibly nicer than stock shadcn’s flat gray
--destructive danger.solid name translation only
--input border.field the catalog’s field border (proposal 0005): derived while it aliases border.base, native once authored
--ring ring derived from primary, lightened in dark mode. The colour only: shadcn sets the ring’s width in component classes, so ring.width and ring.offset are dropped
--chart-1…5 palette.categorical.1–5 derived data-viz palette anchored on your brand hue
--sidebar-* elevation/text/primary/accent grid cells exporter convention; properly a component-tier concern (v2)
--radius radius.md shadcn derives sm/md/lg/xl from it via calc() (sm = −4px, md = −2px); a radius.md between 0 and 4px collapses sm (and md) to 0, reported as TST2104
--font-sans / --font-mono font.sans / font.mono v4: @theme; v3: fontFamily in the config snippet

Every mapping decision is recorded per-variable in report.json with its coverage class and provenance.

A slot’s own $description and $deprecated are written as comment lines above its variable in :root (both eras), not repeated under .dark.

Mode handling#

color-scheme maps to shadcn’s class strategy: :root gets light values, .dark gets dark. This binds mode names: a dark-native design system (Cathode) compiles correctly, its native look under .dark.

Out of scope#

  • components.json with cssVariables: false (utility-class theming): without variables there’s no theme artifact. Theming would mean rewriting your component files, which Transtyle will never do.
  • Non-Tailwind projects: shadcn itself requires Tailwind. The :root/.dark blocks are self-contained CSS if you want just the variables; the dedicated framework-free css-variables exporter covers this properly.

Using the output#

Each build writes a usage.md with era-specific paste-in steps next to the theme. Short version, for v4: import globals.transtyle.css after Tailwind and toggle dark on <html>. For v3: import the globals file, merge the .cjs theme into tailwind.config, set darkMode: ["class"].

Contrast, motion and brand#

When a design system declares contrast or brand, the theme gains blocks after .dark: [data-contrast="more"], [data-brand="globex"], and a compound block such as .dark[data-brand="globex"] wherever the separate blocks would leave a light value on the dark page. Each holds only the variables that change. The more contrast block is repeated inside @media (prefers-contrast: more). The user’s system setting applies until you set data-contrast yourself. Put the attributes next to the dark class: <html class="dark" data-brand="globex">. motion is reported dropped, because shadcn’s theme has no duration variable.