Infrastructure for design token governance.
Underlith turns design decisions into enforceable systems.
Most teams rebuild their design system after a stack migration — not because the decisions changed, but because those decisions were never separated from the implementation.
Colors hardcoded in components.
Spacing defined per framework.
Design intent scattered across codebases.
When design decisions live inside implementations, they disappear every time the stack changes.
Underlith separates design decisions from implementation and gives teams the infrastructure to govern them across frameworks and platforms.
One versioned token layer.
Every framework.
Every product.
Underlith is not your source of truth — it's the infrastructure to build one.
Documentation
https://mikaelcarrara.github.io/underlith
To ensure consistent diffs and behavior across operating systems:
- All text files are stored with LF line endings in the repository
- .gitattributes enforces LF for text files and marks binaries
- .editorconfig configures editors to save with LF and common formatting basics
- On Windows, the repository config uses core.autocrlf=input and core.eol=lf to avoid CRLF conversions
- CI validates EOL policy: fails if CRLF is present or if .gitattributes would renormalize files
This keeps the repo clean and prevents spurious EOL diffs in PRs.
- Framework agnostic — plain CSS, Tailwind, Sass, CSS-in-JS, or any styling solution
- Brand layer — separates identity from framework primitives
- Publishable —
@your-org/tokensbecomes the shared source of truth across products - W3C aligned — follows the W3C Design Tokens Community Group architecture
- Automation ready — designed for CI pipelines, linters, and AI-assisted migrations
Underlith separates design decisions from implementation through three layers and a publishing pipeline.
Base tokens → Brand tokens → @your-org/tokens
(framework (your org's (published package,
primitives) identity) consumed everywhere)
--ul-space-4 --ul-color-brand-primary npm install @acme/tokens
--ul-radius-md --ul-color-text @import "acme.brand.css"
--ul-duration-fast --ul-color-bg-surface change once, update everywhere
| Layer | Responsibility | Package |
|---|---|---|
| Base tokens | Raw scales — space, type, radius, motion, opacity | @mikaelcarrara/underlith |
| Brand tokens | Your organization's identity values | @your-org/tokens |
| Semantic aliases | Intent over value (--ul-color-text, --ul-color-bg-surface) |
@your-org/tokens |
Components consume semantic aliases.
When the brand evolves, aliases change — components don't.
npm install -g @mikaelcarrara/underlithThis makes the underlith command available globally.
# Install package locally to access starter tokens
npm install @mikaelcarrara/underlith
# Copy starter tokens file
cp node_modules/@mikaelcarrara/underlith/src/underlith.tokens.css .
# Scaffold brand package
underlith brand init --org your-npm-username
# Publish package
cd ./@your-npm-username/your-package-name
npm publish --access publicEvery product in your organization can now install your token package.
# Install CLI
npm install -g @mikaelcarrara/underlith
# Map existing CSS variables
underlith init --shadcn --globals ./styles/globals.css
# Copy tokens file if needed
cp node_modules/@mikaelcarrara/underlith/src/underlith.tokens.css .
# Scaffold brand package
underlith brand init --org your-npm-username
# Publish
cd ./@your-npm-username/your-package-name
npm publish --access publicFrom here, every product installs your token package and inherits your brand decisions automatically.
Once you have published @your-org/tokens, that is the only import your products need.
Underlith is the infrastructure that generated it — your token package is the source of truth.
@import "@your-org/tokens/your-org.brand.css";
color: var(--ul-color-text);
gap: var(--ul-space-4);
border-radius: var(--ul-radius-md);@import "@your-org/tokens/your-org.brand.css";
@theme {
--color-brand: var(--ul-color-brand-primary);
--font-sans: var(--ul-font-sans);
--radius-md: var(--ul-radius-md);
}@import "@your-org/tokens/your-org.brand.css";
@mixin button-primary {
background: var(--ul-color-brand-primary);
border-radius: var(--ul-radius-md);
}// globals.css already imports @your-org/tokens — just consume the tokens
const Button = styled.button`
padding: var(--ul-space-2) var(--ul-space-4);
background: var(--ul-color-brand-primary);
color: var(--ul-color-text-inverse);
border-radius: var(--ul-radius-sm);
transition: background var(--ul-duration-fast) var(--ul-ease-inout);
`;.tooltip {
transition: opacity var(--ul-duration-fast) var(--ul-ease-inout);
}
.skeleton {
animation: var(--ul-motion-skeleton);
}Motion tokens automatically respect prefers-reduced-motion: reduce.
Update the token in your brand package, bump the version, and publish.
# Edit underlith.tokens.css in your @your-org/tokens package
# then:
npm version minor
npm publish --access publicEvery product inherits the change on npm update @your-org/tokens.
No component changes. No manual propagation.
- Getting Started
- Tokens Reference
- Reference Components
- Consumption Strategies
- W3C Alignment
https://mikaelcarrara.github.io/underlith
- Overview
- Architecture
- Roadmap
- Vision
- Stewardship
- CI & Automation
- Governance
- Contributing
- Designer Guide
MIT © 2026 Mikael Carrara