| name | unify-theme |
| description | Use when the user asks to set up, install, or migrate to the Unify Theme for reablocks — a production-ready theme pack built on a three-level design-token system (primitives → semantic → Tailwind utilities) synced with the Unify Figma library. Covers the single-file `themeUnify.ts` TypeScript download, the Reablocks Figma Plugin export for CSS layers, file layout (root.css / dark.css / light.css / tw.css / common.css), per-component detail tokens (e.g. `--buttons-details-height-core-icon-lg`), re-branding via primitive scale, customization without TypeScript changes, and Tailwind v4+ wiring. |
Unify Theme
The Unify Theme is a complete, production-ready theme pack for reablocks built on a three-level design-token system that exposes tokens at every level and for every component — not just primary / secondary / accent, but per-component detail tokens like --buttons-details-height-core-icon-lg, --inputs-details-corner-radius-primary, and --tabs-details-stroke-width-underline-lg.
This means you can re-skin Unify at any granularity:
- Change the brand color → every component updates.
- Change the semantic
--background-brand-base → every brand surface updates.
- Change just
--buttons-details-corner-radius-base → only button radii change.
When to use Unify
Use Unify when any of these apply:
- The user mentions Figma, the Unify Figma library, or the Reablocks Figma Plugin.
- The user wants design tokens synced end-to-end from Figma to production.
- The user wants to re-skin reablocks at fine granularity (per-component dimensions, radii, padding) without writing Tailwind overrides or per-component TypeScript.
- The user references
themeUnify.ts, root.css / dark.css / light.css / tw.css, or per-component detail tokens like --buttons-details-*, --inputs-details-*, --tabs-details-*.
- The user is on Tailwind v4+ and wants a complete token system rather than ad-hoc class overrides.
Do not use Unify when:
- The user just wants minor color/variant tweaks → use
extendTheme(theme, …) against the default theme instead (see main reablocks skill).
- The user is not on Tailwind v4+ or reablocks v10+ → fall back to the default theme.
- The user explicitly asks for the default theme.
Three-level token architecture
Unify is structured as three layers that cascade. A change in Figma travels down through all three and reaches every reablocks component without any code change.
FIGMA VARIABLES
│ export via Figma plugin
▼
┌──────────────────────────────────────────────────────────────────────┐
│ LEVEL 1 · PRIMITIVES root.css │
│ • Color scales (50–1000 + alpha) │
│ • Spacing scale │
│ • Sizing scale │
│ • Corner-radius scale │
│ • Per-component detail tokens │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ LEVEL 2 · SEMANTIC TOKENS dark.css / light.css │
│ Role-named aliases — one file per Figma mode. │
│ background-*, content-text-*, content-assets-*, stroke-*, │
│ effects-*, gradient-* │
└──────────────────────────────────────────────────────────────────────┘
│ registered in
▼
┌──────────────────────────────────────────────────────────────────────┐
│ LEVEL 3 · TAILWIND UTILITIES tw.css │
│ @theme inline { … } — exposes every token as a Tailwind utility │
└──────────────────────────────────────────────────────────────────────┘
│ consumed by
▼
reablocks components (no code changes)
The per-component detail tier in Level 1 is what makes Unify uniquely customizable — you can tune individual component dimensions (button heights, input padding, tab underline thickness) without writing Tailwind overrides, because each component theme already references those variables through arbitrary-value classes like h-(--buttons-details-height-core-icon-lg).
Exporting tokens from Figma
The official Reablocks Figma Plugin keeps designs and code in sync without hand-copying tokens.
Running the plugin
- Open the Figma design file.
- Run Plugins → Reablocks Figma Plugin → Select Mode Variant (Light/Dark) → Generate.
- Drop the generated tokens into the styles directory (
root.css, dark.css, light.css, tw.css).
Download the pre-built TypeScript theme
For the TypeScript half of Unify, grab the single-file bundle — every component theme plus the composed ReablocksTheme in one file:
📄 Download themeUnify.ts
Drop it anywhere in src/ (e.g. src/themeUnify.ts or src/shared/utils/Theme/themeUnify.ts) and import the composed theme:
import { theme } from './themeUnify';
Then jump to Wire it up at the app root. The CSS token layers (root.css / dark.css / light.css / tw.css) still come from the Figma plugin export — themeUnify.ts references those CSS variables and Tailwind utilities and expects them to be present in the stylesheet.
Prefer per-component files for easier diffing and overrides? themeUnify.ts also exports each component theme individually (buttonTheme, inputTheme, …), so you can split it back into the Optional: split per component layout below by hand, or use it as-is.
Prerequisites
npm install reablocks
npm install -D tailwindcss @tailwindcss/postcss postcss postcss-nested postcss-preset-env
Unify is built for Tailwind CSS v4+ and reablocks v10+ with React 18+.
File layout
The theme is split into two parts: CSS token files (generated by the Figma plugin) and a TypeScript component-theme module (the downloadable themeUnify.ts).
Recommended: single-file TypeScript theme
The simplest layout — one TS file alongside the CSS layers from the plugin:
src/
├─ assets/
│ └─ styles/
│ ├─ index.css # entry — imports the others
│ ├─ common.css # base body styles & global resets
│ ├─ root.css # Layer 1 — primitive tokens (incl. per-component detail)
│ ├─ dark.css # Layer 2 — semantic tokens (default / dark mode)
│ ├─ light.css # Layer 2 — semantic tokens overridden for light mode
│ └─ tw.css # Layer 3 — Tailwind @theme registration
└─ themeUnify.ts # every component theme + composed ReablocksTheme
Optional: split per component
If you'd rather maintain each component theme in its own file (easier to diff and review individual changes), split themeUnify.ts along its // region markers:
src/
├─ assets/styles/ # same as above
└─ shared/
└─ utils/
└─ Theme/
├─ index.ts
├─ theme.ts # composes ReablocksTheme
└─ components/ # one file per component theme
├─ ButtonTheme.ts
├─ InputTheme.ts
├─ SelectTheme.ts
└─ …
Both layouts are functionally identical. The only hard requirement is the CSS import order, before the app renders.
Step 1 — Install the CSS token layers
index.css — entry point
@import "./common.css";
@import "./light.css";
@import "./dark.css";
@import "./root.css";
@import "./tw.css";
Import order matters. light.css and dark.css must come before root.css is fully registered as Tailwind's source of truth, and tw.css must come last so it can reference all the variables.
common.css — global base styles
body {
color-scheme: dark;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
@apply font-sans text-base;
line-height: 1.3;
}
body,
#root {
margin: 0;
display: flex;
min-height: 100vh;
width: 100vw;
@apply bg-background-neutral-canvas-base text-content-text-on-color-light-dark;
}
root.css — Layer 1: primitives + per-component detail
The raw palette plus per-component detail tokens. This is the file you regenerate from Figma on every design-system bump:
- Color scales — every hue has a 50–1000 scale plus an alpha (
-a-) variant.
- Spacing —
--spacing-padding-{4xs..8xl} and --spacing-space-between-{3xs..4xl}.
- Sizing —
--sizing-asset-{3xs..4xl}, --sizing-size-tokens-{4xs..6xl}, --sizing-dividers-details-*.
- Corner radius —
--corner-radius-{sharp, sm, primary, lg, pill}.
- Per-component detail tokens — what makes Unify deeply customizable:
--buttons-details-height-core-icon-lg, --inputs-details-corner-radius-primary, --tabs-details-stroke-width-underline-lg, --calendar-details-vertical-padding-inside, --navigation-details-corner-radius-row-item, --notifications-details-asset-size-base, --selectors-details-width-toggle-lg, --table-details-padding-row-cell-lg, …
:root,
:host {
--color-blue-hyperstream-500: #3476ff;
--color-blue-hyperstream-600: #105eff;
--spacing-padding-base: 16px;
--corner-radius-primary: 6px;
--buttons-details-height-core-icon-lg: var(--sizing-size-tokens-base);
--inputs-details-corner-radius-primary: var(--corner-radius-primary);
--tabs-details-stroke-width-underline-lg: 2px;
}
dark.css — Layer 2: semantic tokens (default)
Maps role-named aliases onto primitives:
| Family | Examples | Purpose |
|---|
background-* | background-brand-base, background-neutral-canvas-base, background-neutral-raised-1..6 | Surfaces, raised cards, canvas |
content-text-* | content-text-neutral-base, content-text-brand-1, content-text-semantic-warning-2 | Text colors |
content-assets-* | content-assets-brand-base, content-assets-semantic-error-1 | Icon / asset fills |
stroke-* | stroke-neutral-3, stroke-brand-base, stroke-focused-highlight | Borders / outlines |
effects-* | effects-shadows-base-md, effects-focused-base | Shadows and focus rings |
gradient-* | gradient-brand-50, gradient-neutral-700 | Multi-stop gradient stops |
:root,
:host {
--reablocks-theme: dark;
--background-neutral-canvas-base: var(--color-neutrals-darth-abyss-a-1000);
--background-brand-base: var(--color-blue-hyperstream-a-1000);
--background-semantic-error-base: var(--color-red-crimson-wrath-a-1000);
--content-text-neutral-base: var(--color-neutrals-kyber-crystal-a-1000);
--content-text-brand-base: var(--color-blue-hyperstream-a-1000);
--stroke-neutral-3: var(--color-neutrals-kyber-crystal-a-300);
--stroke-focused-highlight: var(--color-blue-hyperstream-a-500);
}
light.css — Layer 2: light-mode overrides
Same shape as dark.css but scoped to a selector that activates light mode. Unify recognizes six selector aliases so any theme-switch library works: .theme-light, .light, [data-theme='light'] (and their &-prefixed forms).
:root,
:host {
.theme-light,
&.theme-light,
.light,
&.light,
[data-theme='light'],
&[data-theme='light'] {
--reablocks-theme: light;
--background-neutral-canvas-base: var(--color-neutrals-kyber-crystal-a-1000);
--content-text-neutral-base: var(--color-neutrals-darth-abyss-a-1000);
}
}
Because both modes target the same semantic names, every component automatically re-skins when the theme attribute changes — no component code to update.
tw.css — Layer 3: Tailwind registration
This is the bridge: it tells Tailwind v4 to expose every CSS variable as a utility class.
@import 'tailwindcss'; — pull Tailwind v4 in.
@source "node_modules/reablocks"; — keeps reablocks's compiled classes alive through Tailwind's purge.
@custom-variant dark/light/disabled-within — registers variant selectors matching the class/attribute on the root element.
@theme inline { … } — maps every CSS variable to a --color-*, --radius-*, --text-*, --font-* token Tailwind recognizes.
@import 'tailwindcss';
@source "node_modules/reablocks";
@source inline("line-clamp-{1..10}");
@custom-variant disabled-within (&:has(input:is(:disabled), textarea:is(:disabled), button:is(:disabled)));
@theme inline {
--font-sans: 'Inter', sans-serif;
--font-mono: 'Share Tech Mono', monospace;
--radius-primary: var(--corner-radius-primary);
--radius-lg: var(--corner-radius-lg);
--radius-pill: var(--corner-radius-pill);
--color-red-*: initial;
--color-blue-*: initial;
--color-green-*: initial;
--color-background-brand-base: var(--background-brand-base);
--color-content-text-neutral-1: var(--content-text-neutral-1);
--color-stroke-semantic-error-3: var(--stroke-semantic-error-3);
}
The full file is generated alongside root.css by the Figma plugin and produces utilities like:
bg-background-brand-base / bg-background-neutral-raised-2
text-content-text-neutral-1 / text-content-text-semantic-error-base
border-stroke-brand-3 / border-stroke-neutral-5
fill-content-assets-brand-base
rounded-(--corner-radius-primary) (arbitrary-value form)
h-(--buttons-details-height-core-icon-lg) (per-component detail)
postcss.config.ts
export default {
plugins: {
'@tailwindcss/postcss': {}
}
};
Step 2 — Add the component-theme module
The TypeScript half maps reablocks's ReablocksTheme slots onto Tailwind utilities that reference the CSS tokens. Each component theme references both Tailwind utilities (semantic colors) and the per-component detail variables (dimensions, radii, gaps), so component dimensions are tunable from CSS alone.
For example, the button theme references the detail tier directly:
sizes: {
small: 'h-(--buttons-details-height-core-icon-sm) text-xs px-(--buttons-details-horizontal-padding-sm)',
medium: 'h-(--buttons-details-height-core-icon-md) text-sm px-(--buttons-details-horizontal-padding-md)',
large: 'h-(--buttons-details-height-core-icon-lg) text-base px-(--buttons-details-horizontal-padding-lg)'
}
Change --buttons-details-height-core-icon-md in root.css and every medium button across the app re-flows — no component override, no Tailwind class change.
Single-file shape (themeUnify.ts)
import { type ReablocksTheme } from 'reablocks';
export const arrowTheme = { };
export const buttonTheme = { };
export const inputTheme = { };
export const theme: ReablocksTheme = {
components: {
arrow: arrowTheme,
button: buttonTheme,
input: inputTheme,
}
};
Split-layout root composition (shared/utils/Theme/theme.ts)
import { type ReablocksTheme } from 'reablocks';
import { arrowTheme } from './components/ArrowTheme';
import { avatarTheme } from './components/AvatarTheme';
import { buttonTheme } from './components/ButtonTheme';
import { inputTheme } from './components/InputTheme';
import { selectTheme } from './components/SelectTheme';
import { tabsTheme } from './components/TabsTheme';
export const theme: ReablocksTheme = {
components: {
avatar: avatarTheme,
arrow: arrowTheme,
button: buttonTheme,
input: inputTheme,
select: selectTheme,
tabs: tabsTheme,
}
};
Components included
| Category | Components |
|---|
| Inputs | input, textarea, select, dateInput, checkbox, radio, toggle, range, field |
| Actions | button, chip, kbd, pager, sort |
| Overlays | dialog, drawer, tooltip, popover, menu, contextMenu, commandPalette, notification, backdrop |
| Layout | card, divider, collapse, tabs, stepper, breadcrumbs, callout |
| Data | tree, jsonTree, list, calendar, calendarRange, redact, ellipsis, dateFormat |
| Display | avatar, avatarGroup, badge, arrow, skeleton, dotsLoader, typography |
| Navigation | navigation |
Wire it up at the app root
import { ThemeProvider } from 'reablocks';
import { theme } from './themeUnify';
import './assets/styles/index.css';
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
Unify-specific customization
For generic single-component overrides via extendTheme, see the main reablocks skill. The patterns below are unique to Unify because they leverage its token tiers.
Re-brand by replacing the primitive scale
Open root.css and replace the --color-blue-hyperstream-* scale with the new brand hex codes. Every semantic alias (--background-brand-base, --content-text-brand-1, …) and every component that uses them updates automatically.
--color-blue-hyperstream-500: #ff6b35;
--color-blue-hyperstream-600: #e55a2b;
--color-blue-hyperstream-a-1000: #ff6b35;
Better path: do this in Figma and re-export via the plugin.
Tune a single component without code
Per-component detail tokens let you reshape components from CSS alone.
Make every medium button shorter:
--buttons-details-height-core-icon-md: 28px;
Soften input corners across the app:
--inputs-details-corner-radius-primary: 10px;
Thicker tab underline:
--tabs-details-stroke-width-underline-lg: 4px;
Adjust the spacing or sizing scale
Spacing, sizing, and radius scales are exposed as CSS variables in root.css. Change a single value and every component that references it (via Tailwind's p-(--spacing-padding-base) or h-(--sizing-size-tokens-sm) arbitrary-value syntax) re-flows:
--spacing-padding-base: 20px;
--corner-radius-primary: 8px;
--sizing-size-tokens-base: 44px;
Add a new semantic role
Need a tertiary brand or a fifth accent? Add the semantic alias in dark.css (and mirror it in light.css), then register it in tw.css under @theme inline:
--background-tertiary-base: var(--color-purple-fuchsia-a-1000);
@theme inline {
--color-background-tertiary-base: var(--background-tertiary-base);
}
Now bg-background-tertiary-base is a valid utility in components.
Troubleshooting
Tailwind utilities like bg-background-brand-base are unknown.
The @theme inline { … } block in tw.css must register the variable as a --color-* (or --radius-*, --text-*, etc.) for Tailwind to emit a utility. If you added a new semantic token, also alias it in tw.css.
Default Tailwind palettes leak in.
Unify deliberately resets --color-blue-*, --color-red-*, etc. to initial so its own scale owns those names. For a one-off stock-Tailwind color, use an arbitrary value (bg-[#3b82f6]) or remove the relevant initial line in tw.css.
reablocks components render unstyled in production.
Tailwind's purger is dropping reablocks classes. Verify the @source directive in tw.css points at node_modules/reablocks — the path is relative to the CSS file, so monorepos may need a different number of .. segments.
Per-component detail tokens don't take effect.
Verify the override is declared after root.css in the CSS import chain. If you put bespoke tokens in overrides.css, import it last:
@import "./root.css";
@import "./overrides.css";
@import "./tw.css";
Recommended cadence
- Designers keep Figma variables as the single source of truth.
- Engineers re-run the plugin on design-system bumps and commit the regenerated CSS — component themes reference Tailwind utilities, so there is almost never any TypeScript to update.
- Don't hand-edit the generated files; put bespoke tokens in a separate
overrides.css imported after root.css.
Reference
- Token export: Reablocks Figma Plugin
- TypeScript theme download:
themeUnify.ts (single-file, all component themes + composed ReablocksTheme)
- Main reablocks skill (default theme,
extendTheme, hooks): ../SKILL.md