google/frontend-design
Capsem frontend design system. Use when building UI components, styling views, working with the design system, choosing colors, or understanding the component library. Covers the stack (Astro 7 + Svelte 5 + Tailwind v4 + Capsem-owned semantic CSS), color scheme, Svelte 5 rune patterns, data fetching, and code reuse policy.
npx skills add https://github.com/google/capsem --skill frontend-design
index.astro as a thin shellclient:only="svelte"@source directives in global.css)src/styles/capsem-theme.css owns the complete token contract (bg-primary, text-foreground, and peers). Production code must never install, import, scan, or execute Preline or another component library. Historical component references may inspire class composition only; copy the result into Capsem-owned code.tauri::generate_context!() bakes frontend/dist/** into the capsem-app binary at cargo compile time (via the custom-protocol feature). This means:
pnpm run build alone has no effect on a running ./target/**/capsem-app -- the bundle is embedded in the binary.frontend/ change you intend to test in the desktop app, run just build (chains frontend build + cargo build -p capsem-app).just dev ui (cargo tauri dev) bypasses this by loading http://localhost:5173 -- good for iteration, but the production code path goes through the embedded bundle.build YYYY-MM-DD HH:MM:SS as a quick visual sanity check -- if it's stale after you rebuilt, you forgot cargo build -p capsem-app.Also: iframe src for bundled pages must end in index.html (e.g. /vm/terminal/index.html). Tauri's custom protocol on macOS does not auto-append index.html for trailing-slash paths the way Vite/Astro dev server does. A /vm/terminal/ src loads fine in Chrome dev mode and silently 404s in the Tauri app.
Simplicity and correctness above all else. Every line of frontend code must earn its place.
@theme tokens in global.css for domain-specific colors (status, providers, charts)/dev-testing-frontend)references/preline.md and references/preline-docs/ are historical pattern catalogues only. Their upstream install/import examples are forbidden in Capsem; the checked-in theme and Svelte components are authoritative.references/tailwind.md for Tailwind v4 utility patterns, responsive design, and CSS-first config.references/svelte5.md for Svelte 5 patterns and @sveltejs/mcp CLI doc lookups.references/astro.md for Astro framework patterns (components, content collections, SSR).The UI uses a two-tone surface system. Semantic token names map to specific roles:
| Token | Light | Dark | Role |
|-------|-------|------|------|
| --background | #ffffff (white) | #282828 (rgb 40,40,40) | Main canvas (content area) |
| --background-1 | #f4f3f2 (rgb 244,243,242) | #282828 | Recessed (address bar, inset panels) |
| --background-2 | #f4f3f2 | #282828 | Most recessed (inactive tabs) |
| --layer | #ffffff (white) | #3c3c3c (rgb 60,60,60) | Elevated/selected (active tab, toolbar, cards) |
The pattern: selected = white/lighter, inactive = slightly gray/darker. In dark mode, the base is very dark (#282828) and elevated surfaces pop with #3c3c3c. In light mode, the canvas is white and recessed areas use a warm off-white.
These are set in :root and .dark blocks in global.css. All accent themes share the same surfaces -- only --primary-* changes per accent.
primary tokens (bg-primary, text-primary-foreground, etc.)destructive tokens purple, not red.oklch(0.7 0.15 250) for allowed, purple oklch(0.65 0.15 300) for deniedcapsem-theme.css or the deliberate surface/accent overrides in global.css; theme selection uses data-theme on <html>.All 24 terminal themes (12 families x dark/light) must pass WCAG AA 4.5:1 contrast ratio for foreground text and all 6 ANSI colors (red, green, yellow, blue, magenta, cyan) against their background. This is enforced by theme-contrast.test.ts.
Contrast utilities (parseHex, relativeLuminance, contrastRatio) are exported from themes.ts and used in tests. When adding or modifying terminal themes, run pnpm test to catch any violations.
Use Capsem's semantic token classes for all UI components. Historical pattern references may guide layout, but no upstream CSS or JavaScript may enter the build.
bg-primary text-primary-foreground hover:bg-primary-hover (solid), bg-layer border border-layer-line text-layer-foreground (white), etc.bg-card border border-card-line rounded-xl, headers bg-surface border-b border-card-dividerborder-line-2 rounded-lg bg-layer text-foreground focus:border-primary focus:ring-primarybg-navbar border-navbar-border text-navbar-nav-foreground hover:bg-navbar-nav-hoverbg-overlay border-overlay-border, bg-dropdown text-dropdown-item-foregroundtext-foreground (primary), text-muted-foreground-1 (secondary), text-muted-foreground (tertiary)Do NOT use raw Tailwind colors (bg-gray-200, text-blue-600) for UI chrome. Always use semantic tokens so themes work.
The Appearance section in SettingsPage.svelte is the reference pattern. All dynamic settings sections must match it:
<h2 class="text-xl font-medium text-foreground"> (not font-bold)<h3 class="text-xs font-semibold text-foreground uppercase tracking-wider"> (use text-foreground, not text-muted-foreground-1)bg-card border border-card-line rounded-xl ONLY when it has direct leaf/action children. Groups containing only subgroups render flat (heading + children, no card). This prevents nested grey card boxes.px-4 for horizontal padding, matching the Appearance rows.bg-card border border-card-line rounded-xl mb-3. Never nest inside another card wrapper.text-warning / text-destructive and bg-warning/5 / bg-destructive/10. Never raw Tailwind colors (text-amber-700, text-red-700, bg-amber-50).global.css)Domain-specific tokens defined in @theme { } block:
| Category | Tokens | Purpose |
|----------|--------|---------|
| Status | --color-allowed, --color-denied, --color-caution | Decision states |
| Providers | --color-provider-anthropic, -google, -openai, -mistral | Brand identity |
| Token types | --color-token-input, -output, -cache | Usage tracking |
| Snapshots | --color-snap-manual, -auto | Snapshot types |
| File actions | --color-file-created, -modified, -deleted | FS events |
| Syntax | --color-json-*, --color-sh-* | Code highlighting |
| Spans | --color-span-thinking, -tool, -answer | Trace viewer |
| Charts | --color-chart-grid, -label | Chart infrastructure |
$:)All components and stores use Svelte 5 runes exclusively. No legacy reactive statements.
$state<T>(initial) -- reactive state declaration$derived(expression) -- derived value (recomputes when deps change)$derived.by(() => { ... }) -- derived with complex logic$effect(() => { ... }) -- side effect that re-runs on dependency changes$props() -- type-safe component props with destructuring$state fields (singleton pattern, .svelte.ts extension)onMount for async data loading, onDestroy for cleanup (intervals, charts)// stores/example.svelte.ts
class ExampleStore {
items = $state<Item[]>([]);
activeId = $state<string | null>(null);
active = $derived(this.items.find(i => i.id === this.activeId));
async load() { this.items = await api.getItems(); }
setActive(id: string) { this.activeId = id; }
}
export const exampleStore = new ExampleStore();
<script lang="ts">
let { class: cls = 'size-5' }: { class?: string } = $props();
</script>
<svg class={cls}>...</svg>
Chrome browser shell. Tabs = sessions, toolbar = controls. Views switched by tabStore.active.view:
'new-tab' -- session/profile dashboard (NewTabPage), sortable table of real sessions'terminal' -- sandboxed iframe with xterm.js (VMFrame), one iframe per VM'settings' -- appearance, general, security, network, storage, advanced, aboutTab store (stores/tabs.svelte.ts): openVM() creates a terminal tab or activates existing.
The frontend talks to the backend through capsem-gateway -- a TCP-to-UDS reverse proxy (default port 19222) that forwards HTTP requests to capsem-service over UDS. Bearer token auth is required (token generated at gateway startup, written to ~/.capsem/run/gateway.token).
Key gateway endpoints:
| Endpoint | Purpose |
|----------|---------|
| GET / | Health check (no auth) |
| GET /status | Aggregated VM status (1s cache TTL) |
| GET /terminal/{id} | WebSocket terminal stream |
| Explicit allowlist | Profile, session, stats, enforcement, detection, plugin, MCP, credential, snapshot, and debug routes used by the UI/TUI |
The gateway forwards only routes that are deliberately registered in its route table.
Unknown, retired, or misspelled routes must return 404 instead of falling through to
capsem-service.
Typed data contract:
/vms/{id}/stats/detail,/vms/{id}/timeline, /vms/{id}/security/latest,
/vms/{id}/detection/latest, and protocol-specific routes.
/status,/stats, /vms/list, and profile/plugin/MCP routes.
/inspectsurfaces are burned; UI code reflects typed API contracts only.
Mocks must exercise the same typed routes the product uses.
Before creating new components, stores, or helpers, check what exists:
frontend/src/lib/stores/): extend existing rune storesfrontend/src/lib/components/): extend existing patternsfrontend/src/lib/views/): main view containers with sub-viewsfrontend/src/lib/models/): pure TS business logic (no Svelte deps)api.ts, types.ts): use existing typed route clients, formatters, and typesTake google/frontend-design from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.