Zetta v1.6

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.

Light

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.

Dark

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.

Accessibility

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

1

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.

Detectvar(--color-brand) on a background / border-color of an interactive element; bg-lime-*; a stale #9ae600.

FixMove interactive fills to var(--color-primary); keep brand on the focus ring and spinner.

2

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

DetectA button / active state still filled with brand after the v0.10→v1.2 swap, or the retired #4f46e5 hue after v1.6.

3

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.

DetectA reference to the deleted secondary or quaternary roles, or to tertiary (renamed ai in v1.5).

FixInformational contexts → info; urgency → warning; links → primary; a neutral button surface → surface-muted.

4

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.

5

Charts use the dedicated DataViz ramp — never the palette

Series 1–5 map to chart-1chart-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).

DetectA series assigned a palette role, a semantic colour, or the retired v1.4 ramp (#f43f5e, #84cc16, #f97316…).

6

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.

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

7

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 / main with a non-canvas background; hardcoded #fff blocks under a dark data-theme.

8

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.

DetectHardcoded rgba(…) shadow strings instead of var(--shadow-raised|overlay); a hardcoded scrim instead of var(--color-overlay); raw z-index integers.

Fix1px solid var(--color-border-strong) on structure; var(--shadow-raised) on cards/menus, var(--shadow-overlay) on modals/drawers.

9

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.

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

10

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.

11

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.

Detectoutline: none with no replacement ring.

12

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.

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

13

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

14

Announcement card border

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

15

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

DetectInteractive controls below a 32px hit area.

16

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.

Detecteasing-enter on a hover or a loop; a hardcoded ms or cubic-bezier(); an animation with no reduced-motion fallback.

17

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.

Detectscrollbar-width: none, a hidden scrollbar on a scrollable region, or a browser-default scrollbar left on a themed surface.

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.