A Strict spec is a DESIGN.md file: the format Google Labs published in 2026
(currently alpha), plus one extra top-level key, strict, that says how
tightly generated UI must follow the tokens.
- Any valid
DESIGN.mdis a valid Strict spec. Without astrictblock the defaults below apply. - A Strict spec is still a
DESIGN.md. Other tools seestrictas an unknown key, which the format treats as a warning, not an error.
File layout
---
name: Strict One # required
version: alpha # optional
description: … # optional
colors: { … }
typography: { … }
rounded: { … }
spacing: { … }
components: { … }
strict: { … } # the Strict extension
---
## Overview
…prose an agent reads to understand intent…
Tokens may also be written as ```yaml blocks inside ## sections instead of
front matter. A yaml block only counts as tokens when every top-level key in it
is a spec key; anything else is treated as an illustration. A key may be
defined only once across the whole file.
Tokens
| Group | Value |
|---|---|
colors |
Any CSS color: "#1F3BFF", "oklch(62% 0.18 250)" |
typography |
Object with fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, fontFeature, fontVariation |
rounded |
A length (4px, 0.25rem). Plain numbers are read as px |
spacing |
A length. Plain numbers are read as px |
components |
Object with backgroundColor, textColor, typography, rounded, padding, size, height, width. Variants (hover, pressed) are separate entries |
References point at another token with {group.name}:
components:
button-primary:
backgroundColor: "{colors.primary}"
typography: "{typography.label}" # takes the whole token
padding: "{spacing.sm} {spacing.md}" # substituted as text
Sections
All optional. Those present must appear in this order:
- Overview (or "Brand & Style")
- Colors
- Typography
- Layout (or "Layout & Spacing")
- Elevation & Depth (or "Elevation")
- Shapes
- Components
- Do's and Don'ts
Other headings are kept as custom sections.
The strict block
strict:
version: 1
contrast: AA # AA (4.5:1) or AAA (7:1)
colors:
allow: tokens # tokens: only token colors may appear. any: no check
tolerance: 0.02 # how far a color may drift from a token (OKLab distance, 0–1)
spacing:
allow: scale # scale: only spacing tokens. any: no check
extra: ["0", "1px"] # values allowed in addition to the scale
rounded:
allow: scale
typography:
allow: scale # only the declared sizes, weights and line heights
fonts: declared # only the declared font families
tells: # signs of generic AI-made UI: off, warn or error
purple-gradient: error
emoji-icons: error
nested-cards: warn
glow-shadows: warn
modes: # extra color modes; each overrides base color tokens
dark:
colors:
background: "#0B0B0C"
foreground: "#F5F5F4"
The values shown for contrast, colors, spacing, rounded and
typography are the defaults. tells and modes are empty by default.
Mode overrides are applied before references resolve, so a component that
points at {colors.background} follows the dark value in dark mode.
This package validates the block and applies contrast to the spec's own
components. Checking a project's code and rendered pages against colors,
spacing, rounded, typography and tells is the job of Strict Check
(@strictthemes/check), which has a first version.
Lint rules
| Rule | Severity | Meaning |
|---|---|---|
invalid-yaml, duplicate-key |
error | Tokens can't be read, or a key is defined twice |
missing-name |
error | No top-level name |
duplicate-section |
error | The same section appears twice |
broken-ref, circular-ref |
error | A {reference} points nowhere, or at itself |
invalid-token |
error | A token has the wrong shape |
invalid-color, invalid-dimension, invalid-typography |
error | A value isn't a color, length or font value |
invalid-strict |
error | A setting in the strict block has a value that isn't allowed |
contrast-ratio |
error / warning | A component's text and background fail WCAG. An error when the file has a strict block, a warning otherwise. Checked in every mode |
missing-primary, missing-typography |
warning | No primary color, or no typography tokens |
section-order |
warning | Known sections are out of order |
unknown-key |
warning | Unknown top-level key |
unknown-component-property, unknown-typography-property |
warning | Property the format doesn't define |
unknown-tell, unknown-mode-token |
warning | A tell id that isn't known, or a mode overriding a color that doesn't exist |
missing-sections, orphaned-tokens, token-summary |
info | Sections not present, colors no component uses, token counts |
Exports
| Format | Output |
|---|---|
tailwind |
Tailwind CSS v4 @theme block, plus one class per extra mode |
shadcn |
shadcn registry item (registry:theme) for npx shadcn add <url> |
shadcn-css |
The same theme as :root / .dark CSS variables |
nuxt-ui |
50–950 palettes for Nuxt UI's semantic colors, exact brand and page colors pinned to --ui-*, and an app.config.ts |
dtcg |
W3C Design Tokens Community Group JSON |
Every exported font family ends in a generic fallback (sans-serif, serif or
monospace), so text renders in the right kind of typeface when the web font
is missing. No export loads font files: the project has to add them.
Tailwind
- Type scale. Each typography token becomes a
text-<token>utility that sets size, line height, weight and tracking together (text-h1,text-label). - Spacing. Spacing tokens are written as
--spacing-space-<name>, givingp-space-mdorgap-space-lg. They are not written as--spacing-<name>, because Tailwind would then also use them formax-w-mdandw-lg, and amdof 16px turns everymax-w-mdbox into a 16px sliver.
shadcn
Tested with shadcn CLI 4.23 on a new Vite + Base UI project (shadcn add,
build, light and dark mode in the browser).
- Colors. shadcn has semantic colors (
muted,border,card…) that a spec may not define. Each takes the first matching token name (for exampleborderoroutline), and is derived from the background, foreground and primary colors when there is none. - Muted text.
muted-foregroundis the spec'sneutralcolor when that is readable (4.5:1) on both the page and a card. Otherwise a grey is mixed and darkened until it is. accentis not the brand accent. shadcn paints hover and highlighted states with it, sotertiaryis not mapped to it. Only a token namedaccentis used; otherwise it is derived as a step away from the popover color.- Charts and sidebar.
chart-1tochart-5default to a ramp of the primary color and thesidebar-*variables follow the card, primary, accent and border colors. Tokens with those exact names override them. - Radius.
rounded.mdbecomes shadcn's base--radius, which each shadcn style scales in its own way (in thenovastyle a badge is still a pill at 4px). It is written to:root, where shadcn reads it. - Fonts.
font-sans,font-monoand, when headings use a different family,font-heading. The item'sdocstext, which the CLI prints after install, lists the families to load. - Type scale. The typography tokens are installed as
text-<token>utilities, the same as in the Tailwind export. A token named like one of Tailwind's own sizes (sm,base,lg…) is left out, because shadcn components rely on those sizes. shadcn'scn()takes an unknowntext-*class for a color, socn("text-label text-muted-foreground")drops one of the two until the names are registered as font sizes (createCnfromcn/config, orextendTailwindMerge). Stock components importcnstraight from thecnpackage, so they have to import the configured one fromlib/utils.tsinstead. - Small corners are exact. shadcn derives
rounded-smandrounded-mdfrom the base radius (×0.6 and ×0.8), which gives 2.4px and 3.2px for a 4px base. A rounded token named like one of shadcn's steps, and no larger than the base, is written out as it is, sorounded-smandrounded-mdare the spec's ownsmandmd. Larger tokens are not written: stock components userounded-lgand up for their normal corners, and those stay at the base radius. - Tertiary. shadcn has no variable for a third brand color, and
accentis taken by hover states. Atertiarytoken is exported as--tertiaryand--tertiary-foreground(fromon-tertiary), with the utilitiesbg-tertiaryandtext-tertiary-foreground.
Nuxt UI
Tested with Nuxt UI 4.12 on the official starter (build, light and dark mode in the browser).
- Brand colors. Each of
primary,secondary,success,info,warning,errorandneutralthat the spec defines becomes a 50–950 palette, and--ui-<name>is pinned to the spec's exact value. - Page colors. Nuxt UI takes its background, text and borders from the
neutral palette. When the spec defines a background or foreground color,
--ui-bg*,--ui-text*and--ui-border*are pinned to the spec's colors instead; the in-between shades are mixed from background and foreground. - Dark mode. A spec with a
darkmode pins both. A spec without one pins page colors under:root:not(.dark), so the app's dark mode keeps Nuxt UI's defaults. - Text on solid buttons is Nuxt UI's inverted text color, which is pinned
to the background color. A spec's
on-primaryis not used. - Radius. Nuxt UI's
--ui-radiusis its smallest step, and buttons and inputs use 1.5 times it.rounded.mdis divided by 1.5 so they come out at the spec's value. - Fonts.
--font-sansand--font-mono. Nuxt Fonts, which the starter includes, loads the families by name.