Design-System Conventions
Load write-frontend first. For Svelte components load write-svelte; for every component story or
test load write-frontend-tests. Treat the design system as a public compatibility layer: visual
choices may evolve, but token names, component APIs, behavior, accessibility, and package exports
are consumer contracts.
Load plan-ui-design first when deciding what a flow, pattern, component family, or visual foundation
should be. Keep implementation-only maintenance here; do not reopen settled product design without
evidence. Apply the visual-language implementation rules
when translating screenshot studies or a high-contrast, game-like direction into reusable tokens.
Rendered-UI hard gate: Start the dark-default Storybook with BROWSER=none and --no-open,
inspect the exact changed story in the integrated browser, keep it live, and repeat its freshly
verified Markdown link in every status or final handoff. Never substitute a screenshot or placeholder.
Package boundaries
Keep responsibilities independently consumable:
- tokens: dependency-free CSS custom properties and documented contracts.
- fonts/icons/assets: self-hosted files, declarations, provenance, and third-party licenses.
- uikit: reusable components that consume semantic foundations.
- storybook-config: separately consumable development package exporting the shared theme, preview setup, decorators, docs blocks, addons, and test configuration.
- storybook-workbench: private workspace package containing this repository's stories and docs.
Never make a runtime package depend on Storybook. Keep workbench exports out of the UI package and release target; consumers install the reusable configuration explicitly as development tooling. A platform imports foundations and components explicitly so application code never pays for workbench code. Deploy a static Storybook only through an explicit, reusable documentation workflow; never publish the private workbench itself as a package.
Use one curated, maintained icon source through typed component exports. Bundle custom SVG icons and their provenance with the asset package; do not require a remote icon service or icon font at runtime.
Token architecture
Use three deliberate tiers:
- Bases and preset multipliers define compact scale mathematics.
- Primitive tokens expose generated palettes and metric scales to design-system authors.
- Semantic/component tokens express intent consumed by applications and components.
Rules:
- Reduce independent hand-authored values. Derive spacing, control heights, radii, borders, focus, motion, type, and color scales from small bases plus named multipliers. Use named density, hierarchy, and major-composition ratios instead of applying the golden ratio to every dimension.
- Name semantic tokens by role, not appearance:
surface-canvas,text-muted,action-primary, notdark-grayorblue-9in component CSS. - Consume semantic tokens in components. Primitive steps are implementation detail unless a public foundation API explicitly exposes them.
- Keep calculations in CSS when the browser baseline supports them. Do not add a token build system merely to repeat CSS-native arithmetic.
- Define canonical product colors once as perceptual components and derive every palette, alpha,
overlay, and component state from them with
calc(), relative color syntax, orcolor-mix(). Do not introduce hexadecimal or RGB design values beside the canonical model. Derive transparency from a semantic color instead of maintaining an opaque duplicate with a hand-authored alpha. - Treat color as five linked contracts: hue topology, tone/chroma scaling, output gamut and fallback, semantic foreground/background pairs, and optional graphic effects. Generate them from compact parameters, but expose each layer explicitly enough to test and evolve it independently.
- Use an Oklab-derived model such as OKLCH or OKHSL for perceptual color relationships when it fits the authoring need. Choose absolute or gamut-relative chroma deliberately, inspect every hue after mapping, and record rare optical corrections. Equal parameters do not guarantee equal appearance.
- Use a maintained constant-hue perceptual gamut-mapping implementation. Never clamp converted RGB channels as a palette algorithm: clipping can shift hue and flatten one family more than another.
- Emit a complete sRGB fallback before a Display P3 or other wide-gamut enhancement. Keep runtime packages free of the build-time converter and verify both outputs preserve semantic associations.
- Define grid, trace, outline, glow, gradient, and other visual treatments as semantic effect tokens derived from the same palette and metric scales. Keep them out of text contrast and state meaning; game-like effects default to static and must remain safe under reduced motion and forced colors.
- Build dark neutral ramps with a low-chroma field and a deliberately non-linear tone curve so early surfaces remain dark while upper text steps separate clearly. Build accent ramps with perceptual, gamut-aware curves that preserve vivid upper steps without making every step pastel or fluorescent.
- Set a chroma budget by semantic region. Let one accent dominate, use a second only for a stable distinction, and keep most area neutral; equal access to every palette family is not equal usage.
- Test actual foreground/background pair contrast and rendered vividness in every supported theme. Gate semantic text and control pairs, not merely adjacent primitive steps.
- Do not tune a semantic pair to the exact WCAG threshold. Keep a deliberate safety margin and test computed output on every supported backdrop; color conversion, alpha composition, font rendering, and browser rounding can turn a mathematically borderline pair into a real rendered failure. Fix the shared semantic token rather than weakening a consumer accessibility gate.
- Derive invisible groups and semi-opaque island surfaces from semantic surface, opacity, blur, and elevation tokens. Use backdrop blur as progressive enhancement and retain legible separation without it.
- Make overlay and interaction tokens relative to their intended backdrop. A hover layer must remain distinguishable on canvas, island, glass, popover, and dialog surfaces; do not reuse the same opaque surface token for both the backdrop and the control state.
- Define gradient interpolation in OKLCH or Oklab explicitly. When distant endpoints produce a muddy midpoint, add an intentional transition stop or route instead of falling back to RGB interpolation.
- Derive localized glow from the emitting object's characteristic size and the metric scale; roughly one eighth is a useful initial blur proportion, not a fixed rule. Layer a tighter bright halo with a weaker outer halo. Keep text unblurred and avoid permanent bloom on ordinary controls.
- Use
rem-based spacing and type, unitless line heights, named duration/easing tokens, and logical dimensions. Zero and intrinsic keywords need tokens only when they represent a selectable public design decision. - Keep dark mode the Agora default. Add themes through semantic aliases rather than branching every component selector.
- Change or remove a published token only through the repository breaking-change/version workflow.
Component contracts
- Start from the native element that already owns the semantics and behavior.
- Choose boundaries in this order: composition and gap, surface or opacity shift, elevation, then a visible border when interaction, structure, or forced-colors fallback requires it. Controls may retain borders for affordance; decorative boxes do not earn them by default.
- Treat an island as a composable surface primitive, not a universal card. Keep main content groups transparent when spacing and hierarchy are sufficient, and use borderless semi-opaque islands for floating navigation, tools, overlays, or independently scannable regions.
- Keep shared layout primitives limited to generic geometry, responsiveness, and composition points. Applications own page landmarks, navigation models, route structure, product labels, workflow steps, and final shell compositions; do not ship a demo-shaped application skeleton from uikit.
- Optically align asymmetric icons and marks inside their real control context; do not expose random offsets as consumer props.
- Design components as small semantic bricks. A prop earns its place when it changes semantics, intrinsic behavior or state, or a stable visual variant the component owns. Compose optional content, adornments, actions, and arrangement from snippets, children, and neighboring primitives.
- Split a component when its API starts accumulating presentation toggles, position switches, or mutually dependent options. Do not turn common compositions into one configurable super-component.
- Let a content region accept text shorthand and a framework-native snippet through the same contract. Consumers must be able to compose text, markup, icons, and components without an API workaround. Keep plain strings where the semantic contract requires a stable accessible name or typeahead value; expose a separate visual renderer for collection items.
- Forward the native attributes consumers reasonably need without permitting invalid state combinations. Keep defaults safe, especially button type and form behavior.
- Define hover, active, focus-visible, disabled, loading, invalid, selected, and high-contrast states as applicable. A component is not complete when only its resting screenshot works.
- Give every stateful component one external controller contract and a configurable default controller.
Keep rendered state and semantic transitions in the controller; keep native event translation, DOM
references, focus movement, measurements, and accessibility attributes in the component. Do not
expose both a controller and bindable state props or generic
setStatemethods. - Export component-specific controller types even when several alias the same generic state shape, so consumer APIs and migration guides remain discoverable. Storybook may supply a fixed controller that rejects transitions to keep a review state pinned.
- Define state precedence explicitly. Persistent selected, checked, expanded, invalid, loading, and disabled states outrank transient hover and active treatments; transient feedback must not visually erase or contradict the persistent state.
- Keep an Agora control's selected treatment stable while hovered. Use a modest lightening for hover and localized glow for selection when the component contract calls for it; keep resting, hovered, selected, and disabled emphasis visibly distinct. Preserve each variant's semantic color family unless a cross-family state meaning is explicit.
- Reset native control appearance only within a component that fully restores focus-visible, disabled, invalid, autofill, high-contrast, and platform input behavior. Prefer native controls; implement a custom select, combobox, menu, or dialog only when the native contract cannot satisfy the required composition or behavior, then follow the corresponding APG keyboard and focus model.
- Compose form labels, descriptions, errors, start/end adornments, and actions as optional primitives
or snippets around the essential control. Present a compound control through its wrapper's
:focus-withinstate while focus remains on the native child; suppress hover treatments that obscure focus or invalid state. Keep open lists bounded by a configurable maximum block size with overflow scrolling, disabled options, and an explicit empty selection when the contract permits it. - Share surface, option-row, spacing, radius, and elevation tokens across listbox, menu, and popover families while preserving each widget's distinct selection and keyboard model.
- Keep interactive target size at least the WCAG 2.2 AA minimum; use a larger touch target where the product context allows it.
- Preserve long labels, localization, RTL, text zoom, narrow containers, reduced motion, and forced colors.
- Do not expose implementation selectors as API. Expose a documented CSS custom property only when consumer theming is an intentional contract.
Storybook as the review surface
- Document every public component and every foundation that affects rendering.
- For a component, use a short stable reference: one-line purpose, rendered examples, usage, variants, composition, states, accessibility, and API as applicable. Explain what exists and how to use it. Keep decision history, rejected alternatives, rollout notes, and nonessential rationale out of consumer docs.
- Use Storybook's generated controls and ArgTypes as the API reference; do not repeat them in a handwritten API table. Keep setup separate from examples, and list only packages consumers can actually install.
- Show related variants, sizes, colors, and states together in aligned grids or compact custom renderers. The wrapper must not add row, tile, or hover effects that can be mistaken for component behavior.
- For foundations, render color, typography, spacing, shape, border, elevation, iconography, and motion systems as applicable. Show token names beside rendered values.
- Prefer official maintained Storybook blocks/addons. Write a small local block only when official tooling cannot express the contract; do not add a dependency for a decorative docs widget.
- Default manager, docs, and canvas to the Agora dark theme. Keep a contrasting background option for component verification.
- Align Storybook manager and docs typography and color roles with Agora while retaining Storybook's default spacing and widget geometry unless a verified clash requires a narrow override.
- Start local Storybook for every rendered UI change with both
BROWSER=noneand--no-open; the process must not launch an external browser tab. Wait for readiness and keep it running for the operator's review unless they ask to stop it. - Open the exact changed story or docs route in the integrated browser. Include that live URL as a clickable inline Markdown link in every status or final handoff, re-resolving and repeating it even when it appeared in an earlier response.
- Inspect composed content and combined or transient states in the rendered canvas: selected plus hovered, focused, open, loading, invalid, disabled, long-label, and narrow-container cases as applicable. Do not infer them from selectors or isolated snapshots.
- Run the accessibility panel for every story. Configure automated story accessibility checks to fail where supported, while retaining manual keyboard and assistive-technology review.
Change sequence
- Define or amend the smallest foundation contract.
- Implement the component using semantic tokens and native behavior.
- Add behavior tests and the complete story state matrix.
- Add or update component/foundation documentation.
- Run format, lint, pnpm tests, package/production builds, and Storybook build.
- Start Storybook and inspect the rendered change at narrow and wide viewports, keyboard-only, reduced motion, and relevant theme/contrast settings.
- Inspect each publishable package archive before release.
Do not approve a visual contract from source alone.
Publication hygiene
- Keep package
exportsandfilesallowlists explicit. Exclude tests, stories, fixtures, Storybook, generated reports, and private source. - Ship README, license, declarations, required CSS/assets, and third-party license/provenance files.
- Keep runtime dependencies minimal; load
choose-dependencybefore adding any library. - Test imports through the package root and documented subpaths. Deep source imports are not a supported contract.
- Reproduce a consumer regression against the released package version, then verify the fix through the packed artifact or a released patch in a representative consumer. A workspace link can hide export, dependency, CSS-order, and surface-composition defects that appear only at the public package boundary.
- Build Storybook as a verification artifact, never as an automatically published package or public deployment unless the user explicitly requests and scopes that publication.
- When static Storybook publication is requested, use the shared CI workflow, publish only the generated static artifact, verify its base path and asset loading, and exclude secrets and private environment data.
Review checklist
- Component CSS contains no raw product colors, spacing, radii, borders, type, or motion values.
- Public components remain small semantic bricks; optional presentation composes without boolean or position-prop matrices.
- Bases and multipliers are fewer than generated public choices and have documented meaning.
- Color documentation states the harmony, tone/chroma formula, gamut mapping, fallback, semantic pairings, and effect layer without hiding family-specific exceptions.
- Dark neutral and accent ramps preserve their intended dynamic range in rendered primitive, semantic, control, and navigation contexts; semantic foreground/background pairs pass their gate.
- Translucent interaction layers remain distinct across canvas, island, glass, popover, and dialog contexts without introducing a second hard-coded color system.
- Borders are reserved for meaningful affordance or structure. Invisible groups and translucent islands remain distinguishable through composition, surface, and elevation.
- Glow is localized, stateful, proportional, and absent from text; gradient routes avoid accidental muddy midpoints.
- Semantic names survive a palette or density change.
- Every interaction state is keyboard-operable, visibly focused, and represented in stories/tests.
- Persistent states keep their hierarchy under hover and active input; reusable content slots render text, icon-and-text, and component composition without API workarounds.
- Shared layout exports are domain-agnostic geometry primitives, not application shells or workflow recipes.
- Foundation and component docs render the actual package code.
- Wide-gamut and sRGB renderings preserve role, contrast, and hierarchy; decorative effects do not carry meaning and do not flash or move by default.
- The Storybook workbench remains private, shared configuration stays development-only, and the local server starts without opening an external tab, stays live for review, and is linked in the handoff.
- Package archives contain only supported consumer files and correct licenses.