Zetta v0.10

Guidelines

The 13 guardrails

Zetta's build-time rulebook and migration audit checklist in one. Every rule references tokens, so it holds across Light, Dark and Accessibility. Detection patterns are starting points — colour judgement still needs an eye.

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.

Light

Canvas and cards are white; light surface tints are correct (surface-sidebar #fafafa, surface-muted #bcbcbc, surface-secondary #f4f4f5, semantic *-bg, brand-bg #f0fcd4).

BannedDark / navy fills, colour floods on the canvas, derivative brand shades.

Dark

Slate surfaces (canvas #1d2125, surface-card #22272b, sidebar #161a1d, surface-muted #4c555d).

BannedBleached or white cards, flooded light panels.

Accessibility

Pure black, high-contrast: canvas #000000, all surfaces black. Brand, 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

1

Brand never shifts hue

Primary interactive elements use brand (#b6d600). Hover and active are the opacity tokens brand-hover / brand-active (85% / 70% of brand) — never a different hue, no derivative or tinted lime, in any theme. Focus adds a ring; the fill stays brand.

DetectA green hex other than #b6d600 on interactive elements; a hue change on :hover/:active of a brand fill; bg-lime-* / bg-green-*; a stale #9ae600 (the pre-v0.8 brand).

FixCollapse fills to var(--color-brand) plus the opacity hover/active tokens.

2

Danger is the one variant that shifts colour

button-danger uses danger-solid-hover-active (darkens in Light, lightens in Dark). No other variant shifts colour on interaction — secondary, outline and ghost shift to surface tokens. Always reference the tokens.

DetectNon-danger variants changing fill to a non-surface colour on hover/active.

3

Brand-text is text-only

brand-text (#0a0a31) appears only as text on brand fills. Never use it as a background or fill.

Detect#0a0a31 appearing in background / bg-*.

4

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).

Detectbody / #root / .app / main with a non-canvas background; hardcoded #fff blocks under a dark data-theme; dark fills in Light.

5

Border, not shadow, for structure — and shadows are tokens

Containers separate via a 1px border (border-strong for component edges like cards, inputs and tables; hairline for subtle dividers). Shadows are shadow-sm/md/lg tokens (theme-specific; none in Accessibility) reserved for overlays and elevated surfaces only — dialog, drawer, popover, dropdown, tooltip, command palette, toast. The modal scrim is overlay.

Detectbox-shadow on cards / tables / forms; hardcoded rgba(…) shadow strings instead of var(--shadow-*); a hardcoded scrim instead of var(--color-overlay).

Fix1px solid var(--color-border-strong) on structure; var(--shadow-md) on overlays.

6

Two radii

Component containers use rounded.base = 8px; pills, avatars, switches and progress use rounded.full; full-bleed uses rounded.none. The only smaller radius is the checkbox → rounded.sm (4px).

Detectborder-radius other than 8px / 9999px / 0 (then allow the 4px checkbox); rounded-(md|lg|xl) on components.

7

Type roles and floor

Inter = structure (display, headings); Geist = operation (body, label, nav, caption); Geist Mono = code and OTP. There is a 12px floor (caption). Never mix Inter and Geist within one element.

8

Semantic = status only; fill ≠ text; charts are their own scale

Semantic colours communicate status, never interaction (button-danger excepted). Fill tokens (success) are for icons and decoration; text tokens (success-text) are contrast-safe foregrounds — never swap them. Data-viz uses the dedicated chart-1…5 orange ramp, not brand or semantic colours; gridlines use border-strong. In Accessibility the ramp is greyscale and requires non-colour separation (pattern, dash, label).

9

Active sidebar nav

The active sidebar nav item takes brand-bg (a lime tint) with a brand icon and bold weight.

10

Announcement card border

An announcement card carries border-announcement (high-contrast against the canvas) on the border only — the surface stays the card surface.

11

Focus = a 2px ring

Focus adds a 2px border-focus outline at 2px offset (button-danger uses danger). border-focus is lime in Light/Dark, white in Accessibility — reference the token. Every interactive component ships an explicit focus variant; apply it. Note: border-strong is a neutral grey structural border (it was lime in ≤v0.5) — only border-focus is lime.

Detectoutline: none with no replacement ring.

12

Icons = Material Symbols Outlined

Material Symbols Outlined at weight 300; FILL 1 only for active toggles. Icon-only controls need an aria-label.

Detectlucide / heroicons / font-awesome imports. (Swapping an existing icon set is nice-to-align, not a blocker.)

13

Accessible sizing

Interactive targets are ≥ 32×32px in Light/Dark (buttons, inputs, nav items, pagination, toggles, calendar days, carousel controls). 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).

DetectInteractive controls below a 32px hit area; outline: none without a replacement.

Where the spec is silent

Build from the foundation tokens plus these guardrails and state your assumptions for areas v0.10 does not yet specify: per-component motion mapping, data-viz chart structure (axes / tooltips / chart types), empty states, z-index / stacking, form-validation UX, theme-transition behaviour, 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.