Guidelines
The 17 guardrails
Zetta's build-time rulebook and migration audit checklist in one. Every rule references tokens, so it holds across Light, Dark and Accessibility. The headline: Brand steps back to identity; Indigo Primary is every interaction.
Allowed vs banned surfaces, per theme
The "canvas integrity" rule is about the main background and structural blocks — not a theme's own legitimate surface tints. In a migration, strip the dark-in-light / bleached-in-dark / derivative fills, never a theme's real tints.
Canvas and cards are white; light surface tints are correct (surface-sidebar #f8fafc, surface-muted #f4f4f5, surface-secondary #e4e4e7, semantic *-bg, primary-bg #eef2ff, brand-bg #f0fcd4).
BannedDark / navy fills, colour floods on the canvas, palette colours as large backgrounds.
Slate surfaces (canvas #1d2125, surface-card #22272e, sidebar #161b22, surface-muted #2d333b); the palette shifts to its 400-series.
BannedBleached or white cards, flooded light panels.
Pure black, high-contrast: canvas #000000, all surfaces black. Brand, primary, borders and semantics are white; semantic backgrounds are black. Meaning is carried by label + icon + border.
BannedAny colour fill used to convey state; shadows (all none).
The checklist
Brand = identity only
Lime brand (#b6d600) is identity only — logo, focus ring, spinner, banner icon, decorative identity icons, and the brand icon-tile. It is never a button, CTA, link, active state, badge, or chart colour.
Primary = every interactive state
Indigo primary (#5155C6 Light / #818cf8 Dark) is every CTA, hover, active / selected / checked, progress and slider fill, primary badge, and in-chrome link. Text on a primary fill is primary-text (#ffffff).
Three palette roles, organised by responsibility
Brand (lime, identity), Primary (indigo, interaction) and AI (purple, automation and premium) each have a distinct job. Use the palette for actions and accents only — never as a large background fill; reach for the -bg tint instead. Neutrals carry ~90% of the UI.
Semantic = status only; fill ≠ text
success / warning / danger / info communicate status, never interaction (button-danger excepted). Fill tokens are for icons, borders and indicators; the -text tokens are contrast-safe foregrounds on the -bg tints — never swap them.
Charts use the dedicated DataViz ramp — never the palette
Series 1–5 map to chart-1…chart-5 directly. They no longer map to palette roles — v1.5 severed that link and fully retuned both ramps, so a chart must never pull primary, ai or a semantic colour. Beyond five series, repeat the ramp with a pattern or shape cue; gridlines use border-strong. In Accessibility the ramp is greyscale and requires a non-colour cue (pattern, dash, label).
Brand-text is text-only
brand-text (#0a0a31) appears only as text on a brand-lime fill. Never use it as a background or fill.
Canvas integrity
Use the active theme's canvas; never replace or flood it. No dark blocks in Light; no bleached or white blocks in the dark themes (Dark #1d2125, Accessibility #000000).
Border for structure, shadow for lift — all tokens
Containers separate via a 1px border (border-strong for cards, inputs and tables; hairline for subtle dividers). A raised surface may pair surface.raised with shadow-raised; overlays use shadow-overlay. Stacking comes from the z-index scale only. In Accessibility there are no shadows — borders carry all elevation.
Radius scale — four steps
none 0 (tables, full-bleed), sm 4px (badges, chips, tooltips), base 8px (buttons, cards, inputs, modals), full 9999px (pills, avatars, FAB). No ad-hoc radii.
Type roles and floor
Inter for structure (display, headings); Geist for operation (body, label, nav, caption); Geist Mono for code. There is a 12px floor (caption). Never mix Inter and Geist within one element.
Focus = a 2px ring (visibility wins)
Focus adds a 2px border-focus outline at 2px offset — lime in Light/Dark, white in Accessibility. The filled palette button (now just ai) keeps its own-hue ring; the brand button uses ink; danger uses danger. Every interactive component ships an explicit focus variant.
Icons = Material Symbols Outlined, weight 300
Brand colour is allowed only on identity icons; functional, interactive and status icons inherit currentColor / primary / the semantic token. Icon-only controls need an aria-label.
Active sidebar nav = primary-bg
The active sidebar nav item takes primary-bg (a low-saturation indigo tint) with a primary icon and bold weight. (v0.10 used brand-bg — now wrong.)
Announcement card border
An announcement card carries border-announcement (high-contrast against the canvas) on the border only — the surface stays the card surface.
Accessible sizing
Interactive targets are ≥ 32×32px in Light/Dark. The Accessibility theme raises the floor to 44×44px via padding and size overrides. Non-interactive display elements may be smaller (e.g. small avatars).
Motion respects prefers-reduced-motion
Use the duration and easing tokens; wrap every transition in @media (prefers-reduced-motion: reduce) and collapse it to duration-instant. duration-enter / duration-exit do not exist, and easing-enter is for entrances only — never a hover, a bidirectional, or a looping transition. The keyframe library lives in the motion foundation.
Scrollbars are themed
Scrollable containers use scrollbar-track / scrollbar-thumb / scrollbar-thumb-hover at 10px, thumb at rounded.full. WebKit needs ::-webkit-scrollbar; Firefox needs scrollbar-color + scrollbar-width: thin — ship both. See the scrollbar foundation.
Where the spec is silent
Build from the foundation tokens plus these guardrails and state your assumptions for areas v1.6 still leaves open: per-component motion mapping, data-viz chart structure (axes / tooltips / chart types), page-level empty / loading states, form-validation UX, and RTL / localization. Product-level concerns (lifecycle and domain colours, card-header/footer compositions) are deliberately out of core — they live in the product-token layer. See Known gaps for the full list.