@maholan/tokens
v0.3.1
Published
Design tokens for the MHL Untitled UI Platform. This package implements a comprehensive 4-tier token architecture that bridges Figma design files with production code.
Readme
@maholan/tokens
Design tokens for the MHL Untitled UI Platform. This package implements a comprehensive 4-tier token architecture that bridges Figma design files with production code.
Architecture
The token system follows a hierarchical 4-tier architecture:
┌─────────────────────────────────────────────────┐
│ TIER 1 — Primitives │
│ Raw values from Figma Foundation.json │
│ Examples: brand-600, gray-light-300, scale-8 │
└──────────────────────┬──────────────────────────┘
│ composed into
┌──────────────────────▼──────────────────────────┐
│ TIER 2 — Theme Variants │
│ Selectable at install-time via CLI │
│ Dimensions: brand, gray, radius │
└──────────────────────┬──────────────────────────┘
│ mapped to
┌──────────────────────▼──────────────────────────┐
│ TIER 3 — Semantic Tokens │
│ What components consume (mode-aware: L/D) │
│ Examples: bg-primary, text-secondary, border-* │
└──────────────────────┬──────────────────────────┘
│ specialized into
┌──────────────────────▼──────────────────────────┐
│ TIER 4 — Component Tokens │
│ Scoped to specific components │
│ Examples: button-bg-primary, input-border │
└─────────────────────────────────────────────────┘Installation
Interactive CLI Installation (Recommended)
The recommended way to use MHL tokens is through the interactive CLI:
# Initialize your project with theme selection
npx @maholan/cli initThe CLI will guide you through:
- Brand color selection - Choose from 10 color palettes
- Gray base selection - Pick your preferred neutral gray
- Radius style selection - Define corner rounding preferences
- Path configuration - Set component and utility paths
- Preview & confirm - Review before installation
See the CLI flow example for a full walkthrough.
Package Installation
For direct package usage (without CLI):
npm install @maholan/tokens
yarn add @maholan/tokens
pnpm add @maholan/tokensImporting the Token CSS
After installing the package, import the CSS variables into your global CSS
file. Tailwind v4 resolves CSS imports using the "style" export condition,
which is correctly declared in the package exports:
/* globals.css */
@import "tailwindcss";
/* Option A — full path (matches what cli init injects) */
@import "@maholan/tokens/mhl-tokens.css";
/* Option B — shorter alias */
@import "@maholan/tokens/css";This imports the complete @theme { … } block and dark-mode overrides generated
from master.json. All Tailwind classes like bg-brand-600, text-secondary,
ring-primary, shadow-xs resolve to these CSS custom properties.
Semantic tokens use property-specific namespaces so each CSS variable
generates exactly one type of Tailwind utility — --text-color-secondary
generates only text-secondary, not bg-secondary or ring-secondary. See
CSS Utility Naming for the
full reference.
CLI Experience
The CLI provides an interactive, guided experience similar to shadcn/ui:
npx @maholan/cli init
# Interactive prompts:
? Which brand color would you like to use?
› purple (default)
? Which gray base would you like to use?
› neutral (default)
? Which radius style would you like to use?
› base (default)
# Preview theme before installation
Your selected theme:
Brand Color: purple
Gray Base: neutral
Radius Style: base
? Ready to install? (Y/n)Try the demo:
pnpm --filter @maholan/tokens example:cliUsage
Accessing Primitives
import { brandColors, grayLightColors, scale } from "@maholan/tokens";
// Direct primitive access
const brandPrimary = brandColors[600]; // { hex: '#7f56d9', hsl: '259 56% 59%' }
const spacing = scale[8]; // '8px'Using Theme Variants
import { brandThemes, grayThemes, radiusThemes } from "@maholan/tokens";
// Get theme by name
const purpleBrand = brandThemes.purple;
const neutralGray = grayThemes.neutral;
const baseRadius = radiusThemes.base;
// Access specific values
const brandAccent = purpleBrand[600].hsl; // '259 56% 59%'
const textColor = neutralGray[900].hsl; // '23 6% 9%'
const buttonRadius = baseRadius.field.md; // '8px'Building Semantic Tokens
import { buildSemanticColors, brandThemes, grayThemes } from "@maholan/tokens";
// Select themes
const brand = brandThemes.teal;
const gray = grayThemes.steel;
// Build semantic tokens
const semanticColors = buildSemanticColors(brand, gray);
// Use in light mode
const lightBg = semanticColors.light["bg-primary"]; // Uses white
const lightText = semanticColors.light["text-primary"]; // Uses steel-900
// Use in dark mode
const darkBg = semanticColors.dark["bg-primary"]; // Uses gray-dark-950
const darkText = semanticColors.dark["text-primary"]; // Uses gray-dark-50Utility Functions
import { hexToHsl, pxToRem, formatCssVar } from "@maholan/tokens";
// Convert hex to HSL
const hsl = hexToHsl("#7f56d9"); // '259 56% 59%'
// Convert px to rem
const rem = pxToRem(16); // '1rem'
// Format CSS variable name
const cssVar = formatCssVar("brand-600"); // '--brand-600'Theme Dimensions
Brand Colors
10 brand color options:
purple(default) - MHL defaultblue- Professional, corporatecerulean- Fresh, modernteal- Calm, balancedindigo- Deep, sophisticatedfuchsia- Bold, vibrantpink- Playful, energeticmagenta- Creative, artisticorange- Warm, friendlyrose- Elegant, refined
Gray Bases
6 gray base options:
neutral(default) - Warm gray for light modesteel- Cool gray (always used for dark mode surfaces)slate- Blue-tinted grayzinc- True neutralstone- Warm brown-graygray- Balanced gray
Radius Styles
5 radius style options:
base(default) - Balanced 8px roundingsharp- No rounding (brutalist)square- Subtle 4px roundinground- Generous 16px roundingcircular- Pill-shaped fields
Light/Dark Mode
Semantic tokens automatically handle light/dark mode:
import { buildSemanticColors, brandThemes, grayThemes } from "@maholan/tokens";
const colors = buildSemanticColors(brandThemes.purple, grayThemes.neutral);
// Light mode uses selected gray variant
colors.light["bg-primary"]; // white
colors.light["text-primary"]; // neutral-900
// Dark mode ALWAYS uses gray-dark (steel) for surfaces
colors.dark["bg-primary"]; // gray-dark-950
colors.dark["text-primary"]; // gray-dark-50Important: Dark mode surfaces always use the cool gray-dark (steel)
palette regardless of the selected gray variant, ensuring consistent contrast
across all brand color choices.
Type Safety
All tokens are fully typed:
import type {
ColorValue,
ColorScale,
BrandThemeName,
GrayThemeName,
RadiusThemeName,
SpacingToken,
} from "@maholan/tokens";
// ColorValue has both hex and HSL
const color: ColorValue = {
hex: "#7f56d9",
hsl: "259 56% 59%",
};
// Theme names are strictly typed
const brand: BrandThemeName = "purple"; // ✅
const brand2: BrandThemeName = "green"; // ❌ Type errorComponent Integration
Components never reference primitives directly. They use semantic tokens:
// ✅ Correct - uses semantic token
className = "bg-brand-solid text-white";
// ❌ Wrong - hard-coded primitive
className = "bg-[#7f56d9] text-white";CSS Utility Naming — Property-Specific Namespaces
mhl-tokens.css uses Tailwind v4's CSS-first property-specific namespace
pattern (matching the official Untitled UI theme architecture). Each semantic
token is exposed via a namespace that restricts which CSS property the generated
utility applies to:
| CSS variable in @theme | Tailwind utility | Applies to |
| ------------------------ | ----------------------------- | ----------------------- |
| --background-color-X | bg-X | background-color only |
| --text-color-X | text-X | color only |
| --border-color-X | border-X | border-color only |
| --ring-color-X | ring-X | --tw-ring-color only |
| --color-X | bg-X, text-X, ring-X, … | all properties |
The final row (--color-X) is reserved for primitive colors (e.g.
bg-brand-600) and component tokens (e.g. text-btn-icon-primary).
Semantic tokens always use property-specific namespaces so they cannot
accidentally collide.
Why this matters
The previous pattern used --color-secondary: var(--color-bg-secondary), which
caused Tailwind to generate all utilities from that name — including
text-secondary pointing to a background colour. The property-specific
namespace fixes this:
/* OLD — generated text-secondary = background colour (bug) */
--color-secondary: var(--color-bg-secondary);
/* NEW — text-secondary and bg-secondary are independent */
--background-color-secondary: var(--color-bg-secondary); /* bg-secondary */
--text-color-secondary: var(--color-text-secondary); /* text-secondary */Border tokens emit both utilities
Border semantic tokens always generate both border-* and ring-* so
components can use either interchangeably:
--border-color-primary: var(--color-border-primary); /* border-primary */
--ring-color-primary: var(--color-border-primary); /* ring-primary */Dark mode propagation
Property-specific aliases use var() chains pointing at the raw semantic vars
(e.g. --color-bg-primary). Dark mode overrides the raw vars, and the change
propagates automatically through all aliases — no extra .dark {} entries
needed:
/* .dark overrides the raw var */
.dark {
--color-bg-primary: var(--color-gray-dark-950);
}
/* --background-color-primary resolves to gray-dark-950 in dark mode */Available semantic token categories
| Tailwind class prefix | Source category | Example |
| --------------------- | ------------------ | --------------------------------------------------------- |
| bg-* | --color-bg-* | bg-primary, bg-disable-subtle |
| text-* | --color-text-* | text-primary, text-secondary, text-error-primary |
| text-icon-* | --color-icon-* | text-icon-quaternary, text-icon-error-primary |
| border-* | --color-border-* | border-primary, border-error, border-disable-subtle |
| ring-* | --color-border-* | ring-primary, ring-error, ring-error-secondary |
Shadows
All shadow tokens follow the Untitled UI elevation scale. Note: skeuomorphic
is the correct spelling (previously mistyped as skeumorphic in older
releases):
shadow-xs shadow-sm shadow-md shadow-lg shadow-xl shadow-2xl shadow-3xl
shadow-skeuomorphic /* inset border effect */
shadow-xs-skeuomorphic /* xs shadow + skeuomorphic inset combined */
shadow-modern-mockup-inner-{sm,md,lg} /* device mockup inner shadows */
shadow-modern-mockup-outer-{md,lg} /* device mockup outer shadows */Figma Sync
This package is generated from Figma design files:
- Foundation.json → Tier 1 Primitives
- Master.json → Tier 3 Semantic Tokens
To regenerate tokens from updated Figma files, see the Token Architecture documentation.
Package Structure
@maholan/tokens
├── src/
│ ├── primitives/ # Tier 1: Raw values
│ │ ├── colors.ts
│ │ ├── secondary-colors.ts
│ │ ├── scale.ts
│ │ └── typography.ts
│ │
│ ├── themes/ # Tier 2: Theme variants
│ │ ├── brand/
│ │ ├── gray/
│ │ └── radius/
│ │
│ ├── semantic/ # Tier 3: Semantic tokens
│ │ └── colors.ts
│ │
│ ├── types.ts # All type definitions
│ ├── utils.ts # Helper functions
│ └── index.ts # Main export
│
├── assets/ # Figma JSON exports
│ ├── Foundation.json
│ └── Master.json
│
└── ARCHITECTURE.md # Full architecture documentationRelated Packages
@maholan/theme- ThemeProvider and global CSS@maholan/ui- Component library (consumes these tokens)@maholan/cli- CLI tool for theme installation
Registry System (CLI Integration)
The token package includes a comprehensive registry system that enables the CLI to:
- Resolve theme configurations to actual CSS values
- Track which tokens each component needs
- Inject only necessary tokens (deduplicated)
- Support incremental component additions
CLI Workflow
import {
resolveTheme,
formatCssVars,
getComponentTokens,
resolveComponentTokens,
} from "@maholan/tokens";
// 1. CLI Init: Generate initial CSS
const theme = { brand: "purple", gray: "neutral", radius: "base" };
const css = resolveTheme(theme);
const cssText = formatCssVars(css);
// Write cssText to globals.css
// 2. CLI Add: Inject component tokens
const buttonTokens = resolveComponentTokens("button"); // ['brand-600', 'gray-50', ...]
const newCss = resolveTokens(theme, buttonTokens);
// Append only new tokens to globals.css
// 3. CLI Theme: Update theme
const newTheme = { brand: "teal", gray: "steel", radius: "round" };
const updatedCss = resolveTheme(newTheme);
// Rewrite CSS with new theme valuesRun Registry Example
pnpm --filter @maholan/tokens example:registryLicense
MIT
Documentation
For complete architecture documentation, see ARCHITECTURE.md.
