Build the Signal design system: tokens, fonts, theme, density

Self-hosted variable fonts (Overpass/Overpass Mono, OFL-licensed) --
no CDN dependency for the app to render correctly. Dark is the literal
default in tokens.css (:root defines it directly; light is the
override via both prefers-color-scheme and an explicit data-theme),
not a retrofit. Severity tokens collapse OTel's seven severities to
five visual tiers (severity.ts); their translucent -bg variants and
light-mode warn's base color are already tuned for WCAG AA contrast
against an opaque surface, not just the plain page background --
verified with real axe-core runs during the accessibility pass, see
docs/design-system.md.

theme.svelte.ts/density.svelte.ts persist to localStorage and expose
getter/setter functions wrapping module-level $state (Svelte 5's
shared-state-module pattern -- a directly exported $state doesn't
preserve reactivity across modules). app.html's inline script applies
both before first paint to avoid a flash of the wrong theme/density.
This commit is contained in:
2026-08-16 12:35:10 -07:00
parent fb502f3d31
commit a6153c5f90
10 changed files with 451 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
// Theme is dark/light/"system" -- but unlike most apps, an unset
// preference means dark, not system. That's the whole point of "real
// dark mode as the default, not an afterthought": a first-time visitor
// on a light-OS machine still lands in dark, matching the actual usage
// pattern this product is designed for (long sessions, often at night).
// "system" is available for anyone who wants their OS setting to win
// instead, but it's a deliberate opt-in, not the fallback.
//
// The synchronous inline script in app.html applies the same stored
// value before first paint -- this module is what UI controls read/write
// after hydration; the two must stay in sync on the storage key and
// values, not just in spirit.
export type Theme = 'dark' | 'light' | 'system';
const STORAGE_KEY = 'sentry.theme';
function readStored(): Theme {
if (typeof localStorage === 'undefined') return 'dark';
const v = localStorage.getItem(STORAGE_KEY);
return v === 'light' || v === 'system' ? v : 'dark';
}
function apply(t: Theme) {
if (typeof document === 'undefined') return;
if (t === 'system') {
document.documentElement.removeAttribute('data-theme');
} else {
document.documentElement.setAttribute('data-theme', t);
}
}
const initial = readStored();
let theme = $state<Theme>(initial);
apply(initial);
export function getTheme(): Theme {
return theme;
}
export function setTheme(t: Theme) {
theme = t;
apply(t);
try {
localStorage.setItem(STORAGE_KEY, t);
} catch {
// storage unavailable -- the choice just won't persist across reloads
}
}