Velta Design System

Generated token reference · built from app/css/main.css @ bc1f3e8f · v1.5.1. · 2026-11-03 · regenerate with node tools/gen-design-tokens.mjs in the app repo — the suite fails when this page goes stale

Sections: colors · colors-light · colors-brutal · typography · spacing · radii · structure · components · principles · maintenance  — every token has its own id: #--bg, #--accent, #--nb-ink …

Colors

Dark

--bg #0f0f14 — app canvas
--bg-sidebar #181820 — list + drawer
--bg-chat #0b0b10 — chat pane canvas
--bg-hover rgba(244,244,244,.055) — hover overlay — surfaces only, never text
--bg-active #2b5278 — pressed/active fill
--selection-bg #2b5278 — selected-chat fill — its own token, independent of the outgoing bubble (#182)
--bg-bubble-in #1c1c26 — incoming bubble
--bg-bubble-out #2b5278 — outgoing bubble
--bg-reply rgba(244,244,244,.06) — quote/reply strip
--accent #5aa2e6 — actions and links
--accent-2 #8774e1 — p2p + local chat
--accent-tint #5aa2e6 — translucent selection/pressed fills — per theme
--accent-hover color-mix(in srgb, var(--accent) 90%, #f4f4f4) — accent fill hover
--accent-active color-mix(in srgb, var(--accent) 88%, #0b0b10) — accent fill pressed
--on-accent #10131a — ink on accent fills
--accent-text var(--accent) — accent UI text — AA on app surfaces; raw --accent never for normal text in light
--accent-text-in var(--accent) — accent text on incoming bubbles
--focus-ring var(--accent) — keyboard focus outline — one ring color per theme (#182)
--accent-default #5aa2e6 — theme accent for the default swatch
--on-accent-chip #10131a — ink on accent chips
--on-badge #10131a — unread badge ink
--text #f2f2f5 — body ink
--text-dim #8b8b96 — secondary ink
--text-meta-in #8f8f9c — timestamps, incoming
--text-meta-out #a8c6e2 — timestamps, outgoing
--ticks-read #6ec2ff — read receipt ticks
--quote-name var(--accent) — quote sender, incoming
--quote-name-out #d9e9fa — quote sender, outgoing
--green #4fae4f — presence/verified — status, never accent
--danger #e05a5a — destructive
--border rgba(244,244,244,.07) — hairlines
--shadow 0 4px 18px rgba(16,16,16,.45)
--bg-elev #1c1c26 — raised placeholder tiles
--radius-bubble 14px — bubble corner — only per-theme radius token
--head-h 56px — sidebar + chat header (+ safe area)
--list-bar-h calc(64px + env(safe-area-inset-bottom)) — bottom bar (+ safe area)
--font-mono ui-monospace, SFMono-Regular, Menlo, Consolas, monospace — monospace stack — device info, logs
--call-bg var(--bg-chat) — call screen canvas
--call-fg var(--text) — call screen ink
--call-fg-dim var(--text-dim) — call screen secondary
--call-video-fg #f2f2f5 — ink over live video
--call-scrim color-mix(in srgb, #0b0b10 55%, transparent) — controls scrim over video
--call-btn color-mix(in srgb, var(--text) 22%, var(--bg-chat)) — call control fill
--call-btn-on var(--text) — toggled-on call control fill
--call-btn-on-fg var(--bg-chat) — ink on toggled-on control
--call-danger var(--danger) — decline / leave
--call-danger-fg #f4f4f4 — ink on danger control
--call-accept color-mix(in srgb, var(--green) 80%, #0b0b10) — accept
--call-accept-fg #f4f4f4 — ink on accept control
--call-avatar-bg #5a6270 — caller avatar fill
--call-avatar-fg #f4f4f4 — caller avatar ink
--call-pip-bg var(--bg-bubble-in) — self-view tile
--call-btn-size 56px — call control diameter
--ui-zoom 1
--vw calc(100vw / var(--ui-zoom))
--vh calc(100vh / var(--ui-zoom))
--dvh calc(100dvh / var(--ui-zoom))
--bubble-inner-max calc(var(--vw) - 120px)
--msg-font-size 15.5px
--radius-tail 6px
--radius-quote 6px
--radius-media 10px
--on-badge-muted #10131a
--ico-1 14px

Light

--bg #f4f4f4 — app canvas
--bg-sidebar #f4f4f4 — list + drawer
--bg-chat #e7ebf0 — chat pane canvas
--bg-hover rgba(16,16,16,.05) — hover overlay — surfaces only, never text
--bg-active #5aa2e6 — pressed/active fill
--selection-bg #5aa2e6 — selected-chat fill — its own token, independent of the outgoing bubble (#182)
--bg-bubble-in #f4f4f4 — incoming bubble
--bg-bubble-out #e1f0fb — outgoing bubble
--bg-reply rgba(16,16,16,.05) — quote/reply strip
--accent #3390ec — actions and links
--accent-2 #8774e1 — p2p + local chat
inherited from dark
--accent-tint #3390ec — translucent selection/pressed fills — per theme
--accent-hover color-mix(in srgb, var(--accent) 90%, #f4f4f4) — accent fill hover
inherited from dark
--accent-active color-mix(in srgb, var(--accent) 88%, #0b0b10) — accent fill pressed
inherited from dark
--on-accent #10131a — ink on accent fills
inherited from dark
--accent-text #1a68b8 — accent UI text — AA on app surfaces; raw --accent never for normal text in light
--accent-text-in #1a68b8 — accent text on incoming bubbles
--focus-ring #1a68b8 — keyboard focus outline — one ring color per theme (#182)
--accent-default #3390ec — theme accent for the default swatch
--on-accent-chip #10131a — ink on accent chips
inherited from dark
--on-badge #10131a — unread badge ink
inherited from dark
--text #16181d — body ink
--text-dim #5d626d — secondary ink
--text-meta-in #676d7a — timestamps, incoming
--text-meta-out #2a6ea6 — timestamps, outgoing
--ticks-read #1d8bf1 — read receipt ticks
--quote-name #14507e — quote sender, incoming
--quote-name-out #14507e — quote sender, outgoing
--green #2a7032 — presence/verified — status, never accent
--danger #bf2a2a — destructive
--border rgba(16,16,16,.08) — hairlines
--shadow 0 4px 18px rgba(16,16,16,.18)
--bg-elev #e7ebf0 — raised placeholder tiles
--radius-bubble 14px — bubble corner — only per-theme radius token
inherited from dark
--head-h 56px — sidebar + chat header (+ safe area)
inherited from dark
--list-bar-h calc(64px + env(safe-area-inset-bottom)) — bottom bar (+ safe area)
inherited from dark
--font-mono ui-monospace, SFMono-Regular, Menlo, Consolas, monospace — monospace stack — device info, logs
inherited from dark
--call-bg var(--bg-chat) — call screen canvas
inherited from dark
--call-fg var(--text) — call screen ink
inherited from dark
--call-fg-dim var(--text-dim) — call screen secondary
inherited from dark
--call-video-fg #f2f2f5 — ink over live video
inherited from dark
--call-scrim color-mix(in srgb, #0b0b10 55%, transparent) — controls scrim over video
inherited from dark
--call-btn color-mix(in srgb, var(--text) 12%, var(--bg-chat)) — call control fill
--call-btn-on var(--text) — toggled-on call control fill
inherited from dark
--call-btn-on-fg var(--bg-chat) — ink on toggled-on control
inherited from dark
--call-danger var(--danger) — decline / leave
inherited from dark
--call-danger-fg #f4f4f4 — ink on danger control
inherited from dark
--call-accept var(--green) — accept
--call-accept-fg #f4f4f4 — ink on accept control
inherited from dark
--call-avatar-bg #5a6270 — caller avatar fill
inherited from dark
--call-avatar-fg #f4f4f4 — caller avatar ink
inherited from dark
--call-pip-bg var(--bg-sidebar) — self-view tile
--call-btn-size 56px — call control diameter
inherited from dark
--on-badge-muted #f4f4f4

Brutal (neobrutalist, dark only)

--bg #22222b — app canvas
--bg-sidebar #2a2a35 — list + drawer
--bg-chat #1b1b22 — chat pane canvas
--bg-hover rgba(244, 244, 244, .07) — hover overlay — surfaces only, never text
--bg-active #e0b52f — pressed/active fill
--selection-bg #e0b52f — selected-chat fill — its own token, independent of the outgoing bubble (#182)
--bg-bubble-in #2d2d38 — incoming bubble
--bg-bubble-out #e0b52f — outgoing bubble
--bg-reply rgba(18, 16, 22, .08) — quote/reply strip
--accent #8f7ff0 — actions and links
--accent-2 #4fd1c5 — p2p + local chat
--accent-tint #8f7ff0 — translucent selection/pressed fills — per theme
--accent-hover color-mix(in srgb, var(--accent) 90%, #f4f4f4) — accent fill hover
inherited from dark
--accent-active color-mix(in srgb, var(--accent) 88%, #0b0b10) — accent fill pressed
inherited from dark
--on-accent #10131a — ink on accent fills
inherited from dark
--accent-text #a99cf5 — accent UI text — AA on app surfaces; raw --accent never for normal text in light
--accent-text-in var(--accent-2) — accent text on incoming bubbles
--focus-ring var(--accent-text) — keyboard focus outline — one ring color per theme (#182)
--accent-default #8f7ff0 — theme accent for the default swatch
--on-accent-chip #10131a — ink on accent chips
inherited from dark
--on-badge #10131a — unread badge ink
inherited from dark
--text #f2f0e9 — body ink
--text-dim #a3a0ad — secondary ink
--text-meta-in #a19eab — timestamps, incoming
--text-meta-out #55480a — timestamps, outgoing
--ticks-read #6ec2ff — read receipt ticks
inherited from dark
--quote-name var(--accent-2) — quote sender, incoming
--quote-name-out #2b2406 — quote sender, outgoing
--green #3ecf5a — presence/verified — status, never accent
--danger #f47c79 — destructive
--border #45434f — hairlines
--shadow 4px 4px 0 #121017
--bg-elev #2d2d38 — raised placeholder tiles
--radius-bubble 6px — bubble corner — only per-theme radius token
--head-h 56px — sidebar + chat header (+ safe area)
inherited from dark
--list-bar-h calc(64px + env(safe-area-inset-bottom)) — bottom bar (+ safe area)
inherited from dark
--font-mono ui-monospace, SFMono-Regular, Menlo, Consolas, monospace — monospace stack — device info, logs
inherited from dark
--call-bg var(--bg-chat) — call screen canvas
inherited from dark
--call-fg var(--text) — call screen ink
inherited from dark
--call-fg-dim var(--text-dim) — call screen secondary
inherited from dark
--call-video-fg #f2f2f5 — ink over live video
inherited from dark
--call-scrim color-mix(in srgb, #0b0b10 55%, transparent) — controls scrim over video
inherited from dark
--call-btn var(--bg-bubble-in) — call control fill
--call-btn-on var(--text) — toggled-on call control fill
inherited from dark
--call-btn-on-fg var(--bg-chat) — ink on toggled-on control
inherited from dark
--call-danger var(--danger) — decline / leave
inherited from dark
--call-danger-fg var(--nb-ink) — ink on danger control
--call-accept var(--green) — accept
--call-accept-fg var(--nb-ink) — ink on accept control
--call-avatar-bg #5a6270 — caller avatar fill
inherited from dark
--call-avatar-fg #f4f4f4 — caller avatar ink
inherited from dark
--call-pip-bg var(--bg-bubble-in) — self-view tile
inherited from dark
--call-btn-size 56px — call control diameter
inherited from dark
--nb-ink #121017
--nb-main #e0b52f
--nb-main-ink #211d05

Pair table — computed from the tokens above (WCAG 2.x), not hand-measured:

PairRatioGrade
Dark: --text on --bg17.11AAA
Dark: --accent on --bg7.05AAA
Dark: --text on outgoing bubble7.28AAA
Dark: --text-dim on --bg5.67AA
Dark: --text-meta-in on incoming bubble5.29AA
Dark: --text-meta-out on outgoing bubble4.59AA
Light: --text on --bg16.15AAA
Light: --text on outgoing bubble15.27AAA
Light: --text-dim on --bg5.56AA
Light: --accent-text on --bg5.14AA
Light: --accent on --bg3.01AA large/UI
Light: --green on --bg5.51AA
Light: --danger on --bg5.33AA
Light: --accent-2 on --bg3.40AA large/UI

Typography ladder

8.51111.51212.51313.51414.51515.517182022

Single stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif. Half-pixel steps are deliberate (compact ladder).

RoleSizeWhere
Page/brand title22–32drawer name 17, modal title 18, splash 22+
Body / message text15.5msg-text, composer-input
Buttons / rows14–15btn-primary 15, toast 14, info-row 14.5, file-name 14.5
Secondary13–13.5reply 13.5, msg-sender 13.5, day-chip 13, tabs 13.5
Meta11–12.5msg-meta 11.5, chips 11–12.5, service 12.5

Spacing — 2px grid

1†23†45†67†89†1011†1213†1415†161822

Bar height scales with the value. Blue = 2px grid. Amber (†) = odd step, documented exception — not a bug (optical bubble pad, 48px composer/button, media half-bleed, pill chrome). Counts 2026-10-14, padding/margin/gap only (var/calc/env excluded): 1×6, 3×8, 5×17, 7×9, 9×3, 11×3, 13×2, 15×1. On-grid ~89.5%. Do not snap the exceptions. Above the ladder: 24×4, 26×1, 32×1 (structural).

TierValuesUsed for
Gap — tight2 · 4icon↔label, head-actions, meta
Gap — default6 · 8rows, bubbles, most flex stacks
Gap — loose10 · 12 · 14chat-item↔avatar (11†), modals, drawer-head
Padding — tight4–6chips, service-msg, tabs
Padding — standard8 · 10 · 12rows 9†/12, bubbles 7† 10 6†, inputs 10 12
Padding — wide14 · 18composer 14, toasts 14, modal/drawer 18

Radii

Ladder: 6 (small controls) · 8 (rows/buttons) · 10 (cards) · 12 (chips/badges) · 14 (bubbles, --radius-bubble) · 16 (modals/composer) · 999 (pills) · 50% (avatars/round btns). The 9/11/18 one-offs were snapped in d3526f4; the remaining 9px is the pin-tray count bubble — a deliberate half-height circle.

Structural metrics

Token / elementValueNotes
--head-h56pxsidebar + chat header, + env(safe-area-inset-top)
--list-bar-hcalc(64px + safe)46px bar-btn + 18px padding
--radius-bubble14px (dark/light) · 6px (brutal)only per-theme radius token
Bar buttons46px, svg 22sidebar bottom bar
Composermin 48px, svg 26input pad 13/16 tuned to hit 48 with 15.5px text
Icon buttons40px, svg 20–2250% radius
Avatars56 (drawer) · 44 · 42 · 36 · 3422% tile radius, photo pad 2.5%
Shadow0 4px 18px rgba(16,16,16,.45/.18)brutal: 4px 4px 0 ink
--ico-1..514 / 16 / 20 / 22 / 26pxicon ladder: inline · small ui · rows/buttons · bars · composer
Motion.15s hover · .2s panelssee SVG “Motion & states”

Component contracts

Live renders of every component and its states: design/components — generated from the same CSS as the cards below.

How the shared controls are meant to be used — this resolves the ghost-button/link ambiguity from the design review (#182). States everywhere: hover changes surfaces, never text; keyboard focus is a 2px --focus-ring outline; pressed fills use --bg-active / --accent-active.

ComponentClassContract
Button — primary.btn-primaryThe one filled accent action per view (accent fill, --on-accent ink). One per dialog foot or screen; never in lists.
Button — text (tertiary).btn-textCompact accent-text action for rows, footers and secondary choices. It is a BUTTON, not a link: use it for something that happens (Retry, Use for sending), never inline inside a sentence. Borderless — the accent text at weight 600 is the affordance.
Button — destructive.btn-text + --danger inkDestructive = .btn-text colored var(--danger) (Remove, Delete). No filled red variant: destructive is a choice, not a destination — the confirm dialog carries the weight. Red fills are reserved for the call decline control.
Link<a>Navigation, underlined. On app surfaces accent-colored links read --accent-text (AA in every theme); raw --accent is UI-grade only in light.
Icon button.icon-btn40px round, svg 20–22, 50% radius; icon-only controls carry an aria-label. (≥44px effective touch target: under review, #182.)
Tabs.chat-cats / .side-tabsEqual-width chips; active = accent fill + --on-accent-chip ink; a disabled category hides its chip entirely (#75), it never greys out.
Chip.relay-flag, day-chip, bot-chip…Passive metadata pill (meta sizes 11–12.5). Status chips use their status token, never accent.
Text field.text-field15.5px text, --text-dim border, 8px radius. The placeholder is a hint, not a label — label above or aria-label.
Dialog.modalBottom sheet on mobile, centered panel on desktop; one primary action in the foot, cancel as .btn-text; history-back integration follows the modalHistorySettled contract (app AGENTS).
Menu item.ctx-itemRow menu entry: icon + label; hover = --bg-hover surface; destructive entries take --danger ink; focus-visible 2px --focus-ring.

Button states: default → hover (--bg-hover on ghost/tonal, --accent-hover on fills) → pressed (--bg-active / --accent-active) → focus-visible (2px --focus-ring, offset 2px) → disabled (not-allowed cursor, --border edge, dimmed ink — see .ivc-btn, the reference implementation). There is no button-level loading state: async actions disable or move progress to the surface it belongs to (corner pill, transfer pill, steps list) — buttons stay truthful.

Design principles

Pair (WCAG 2.x)RatioGrade
Dark: --text on --bg17.11AAA
Dark: --accent on --bg7.05AAA
Dark: --text on outgoing bubble7.28AAA
Dark: --text-dim on --bg5.67AA
Dark: --text-meta-in on incoming bubble5.29AA
Dark: --text-meta-out on outgoing bubble4.59AA (borderline)
Light: --text on --bg16.15AAA
Light: --text on outgoing bubble15.27AAA
Light: --text-dim on --bg4.93AA (fixed #656a76)
Light: --accent on --bg3.01AA large/UI only (accepted)
Light: --green on --bg3.58UI (fixed #3d9146)