@grassroot/tokens
v0.2.0
Published
Design tokens for `@grassroot/ui-react`. Every component is styled **only** from these tokens (CSS custom properties, surfaced as Tailwind utilities), so a "theme" is just a set of token values under a `[data-theme="…"]` selector. Swap the values, and eve
Readme
@grassroot/tokens
Design tokens for @grassroot/ui-react. Every component is styled only from these
tokens (CSS custom properties, surfaced as Tailwind utilities), so a "theme" is
just a set of token values under a [data-theme="…"] selector. Swap the values,
and every component re-themes — no component code is touched.
Use a built-in theme
@import "tailwindcss";
@import "@grassroot/tokens/base.css"; /* :root defaults */
@import "@grassroot/tokens/themes/neobrutalist.css";
@import "@grassroot/tokens/tailwind.css"; /* tokens → utilities */
@source "../node_modules/@grassroot/ui-react/src"; /* scan component classes */<html data-theme="neobrutalist"></html>For the full switcher, import @grassroot/tokens (all themes) instead of one.
Bring your own theme
The library ships unstyled-friendly. Two ways to make it yours:
1. Start from naked, override the accent
naked is a deliberately neutral baseline (system fonts, zinc greyscale,
near-black accent, standard radii/shadows). Import it, then override the handful
of tokens you care about in your own :root or [data-theme] block:
@import "@grassroot/tokens/base.css";
@import "@grassroot/tokens/themes/naked.css";
@import "@grassroot/tokens/tailwind.css";
[data-theme="naked"] {
/* override after importing */
--accent: #4f46e5; /* your brand colour — UI follows */
--text-on-accent: #ffffff;
--radius-xs: 4px;
}2. Author a theme from the template
Copy src/theme-template.css — a commented skeleton
of every overridable token — rename the selector, fill in values, import it in
place of a built-in theme, and set data-theme to your name. You only override
what you want; everything else falls back to base.css.
Generated token contract
The token package generates its public contract during pnpm build:
generated/token-manifest.jsonrecords each public token's kind, scope, default, provenance, stability, and Studio policy.generated/studio-controls.jsonis the control surface consumed by Theme Studio; it is not a second hand-maintained token list.generated/token-namesprovides the typed public name union and runtime names for integrations.src/theme-template.cssis generated from the same manifest. Edit the authored token sources and metadata, then rebuild.
The manifest currently covers all 77 shipped themes and the global, component,
chart, chart-family, and integration token scopes. Tokens marked hidden are
runtime implementation details and are omitted from the Studio surface.
Module format
The package is ESM-only. package.json declares "type": "module", and the one
JavaScript entry — @grassroot/tokens/token-names — is an ES module. The CSS and
JSON subpaths are unaffected; bundlers resolve those as assets.
./token-names deliberately has no require condition. A CommonJS consumer gets:
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './token-names' is not
defined by "exports" in .../@grassroot/tokens/package.jsonFrom CJS, use await import("@grassroot/tokens/token-names") instead.
Supported Node is >=20, declared in engines. The explicit "type": "module"
is what makes Node 20 load token-names.js as an ES module; without it Node 20
hands the file to the CommonJS loader and throws SyntaxError: Unexpected token
'export'.
The contract
A theme "resolves" once these bottom out to concrete values (enforced by
verify-themes.mjs):
--accent, --surface-canvas, --surface-card, --text-heading,
--text-body, --focus-ring.
Everything else (full neutral scale, semantic states, radii, shadows, fonts,
lines) has a sane default in base.css, so the minimum viable theme is a few
lines. Keep --text-body / --text-muted ≥ 4.5:1 on your surfaces.
Restyling component shape (optional)
Tokens cover colour/radius/shadow. To change a component's structure (e.g. hard offset shadows, square corners) without forking it, target its structural hook under your theme selector:
| hook | component |
| ---------------------------------------------------- | ------------------------------------ |
| .vs-btn | buttons (.vs-btn--bare opts out) |
| .vs-table | tables |
| .vs-chart | chart family (bars/lines/areas/dots) |
| .vs-donut-track, .vs-radar-area, .vs-heat-cell | specific charts |
[data-theme="brand"] .vs-btn:not(.vs-btn--bare) {
box-shadow: 3px 3px 0 var(--border-strong);
}See themes.css (the neobrutalist block) for a worked example.
Editing themes
themes.css is the single source of truth. pnpm build runs
split-themes.mjs (regenerates src/themes/*.css, one importable file per
theme) then verify-themes.mjs (proves every theme resolves). Don't edit
src/themes/*.css by hand — they're generated.
