Files
galaxy-game/ui/docs/design-system.md
T
Ilia Denisov 973480d812
Tests · UI / test (push) Successful in 2m4s
feat(ui): design tokens + light/dark theming, migrate in-game chrome (F1a)
Introduce the shared design-token system under
ui/frontend/src/lib/theme/: tokens.css (dark default + light palette,
plus spacing/radii/typography scales), base.css global baseline
(document background, text, token focus ring, selection), and
theme.svelte.ts (system/light/dark choice, persisted to localStorage,
applied via data-theme on <html>). A pre-paint guard in app.html
resolves the theme before the app boots to avoid a flash, and the theme
picker is wired into the previously-disabled account-menu stub.

Migrate the always-visible in-game chrome to the tokens (header, account
menu, sidebar, tab-bar, bottom-tabs, shell background): dark renders as
before, light comes for free. The default stays dark during the
incremental migration; the remaining view bodies migrate in F1b.

Docs: ui/docs/design-system.md (+ index entry). Test: tests/theme.test.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 07:02:13 +02:00

4.8 KiB

Design system — tokens & theming

The client's visual language lives in a single set of CSS custom properties (design tokens). Components reference the tokens (var(--color-…), var(--space-…)) instead of literal hex/px, so a palette change is a one-file edit and the light and dark themes stay in lockstep.

Where the tokens live

  • src/lib/theme/tokens.css — every token. Theme-independent scales (spacing, radii, typography) sit in :root; the colour/shadow palette is defined twice, once for dark (:root, :root[data-theme="dark"]) and once for light (:root[data-theme="light"]).
  • src/lib/theme/base.css — a small global baseline (document background, default text/typography, a token-driven focus ring, selection colour). Everything else stays in component-scoped <style>.
  • Both are imported once, in the root +layout.svelte; a plain CSS import is global (Svelte only scopes <style> blocks).

Token reference

Colour (theme-dependent)

Token Role
--color-bg Application backdrop, header, bottom-tab bar.
--color-surface Panels (sidebar, cards).
--color-surface-raised Form controls and raised cards.
--color-surface-overlay Floating surfaces (menus, drawers, popovers).
--color-surface-hover Hover state on interactive surfaces.
--color-border-subtle Structural chrome edges.
--color-border Default control borders.
--color-border-strong Emphasis / focus borders.
--color-text Primary text.
--color-text-muted Secondary text.
--color-text-faint Tertiary text, placeholders.
--color-accent (-hover, -active, -contrast, -subtle) Brand periwinkle: links, active states, fills, tints. -contrast is the text colour on an accent fill.
--color-danger (-contrast, -subtle) Errors, destructive actions.
--color-success (-subtle) Success / positive.
--color-warning (-subtle) Warnings.
--color-focus Focus-ring colour (aliases --color-accent).
--shadow-sm / --shadow-md / --shadow-lg Elevation.

*-subtle colours are translucent (rgba) so they tint whatever sits behind them; *-contrast colours are opaque text colours meant to sit on top of the matching solid fill.

Scales (theme-independent)

  • Spacing: --space-1 (0.25rem) … --space-6 (2rem), --space-8 (3rem).
  • Radii: --radius-sm 4px, --radius-md 6px, --radius-lg 10px, --radius-pill.
  • Typography: --font-sans, --font-mono; sizes --text-xs--text-xl; --leading-tight / --leading-normal; weights --weight-normal / --weight-medium / --weight-semibold.

Theming (light / dark / system)

The resolved theme is written to data-theme on <html>, which selects the colour block in tokens.css.

  • A pre-paint guard in app.html reads the stored choice from localStorage["galaxy-theme"] and sets data-theme before the app boots, so first paint never flashes.
  • src/lib/theme/theme.svelte.ts is the runtime store: theme.choice (system | light | dark), theme.resolved (light | dark), and theme.setChoice(…). It persists the choice, applies data-theme, and — while the choice is system — follows OS theme changes via matchMedia.
  • The account menu (account-menu.svelte) exposes the picker. The default is dark; system follows the OS.

The app.html guard and the store deliberately duplicate the resolution logic (one runs before modules load, the other after) — keep them in sync.

Conventions

  • No literal theme colours in component <style>. Use a token; if none fits, add one to tokens.css rather than hard-coding.
  • Map by role, not by hex: a form-control background is --color-surface-raised even if its old value happened to match a hover colour.
  • Directional one-off drop shadows that are not part of the elevation scale may stay as literal rgba(0, 0, 0, …) (they read acceptably in both themes); reach for --shadow-* for standard elevation.
  • Spacing-scale adoption is gradual — colour tokens are the priority; existing one-off paddings are migrated opportunistically, not churned en masse.

Migration status

The token system and the always-visible in-game chrome (header, account menu, sidebar frame, tab bar, bottom tabs, shell background) reference tokens and switch cleanly between light and dark. The view bodies (calculator, inspectors, tables, lobby, auth, map overlays, battle, mail) are migrated incrementally; until a view is migrated its hard-coded colours stay dark in both themes. F1 is complete when no literal theme colours remain in component <style> blocks.