Transcriber UI Style Guide
This guide is for generating new UI elements that match the existing design system.
Design Language
Minimal, developer-tool aesthetic with dark (default), light, and auto themes. Monospaced type throughout. Accent-yellow for primary actions, accent-blue for secondary/server-related actions. Everything is dense and compact — small font sizes, tight spacing.
Theme is controlled by the data-theme attribute on the root element ("dark" | "light" | "auto"). The theme.js utility in static/js/utilities/ handles reading and writing this value. All colours are defined as CSS custom properties in variables.css — never use raw colour literals.
CSS Custom Properties (always use these, never raw colors)
/* Base palette (dark theme — default) */
--bg: #0d0d0f /* page background */
--surface: #141417 /* card / panel background */
--surface2: #1c1c21 /* elevated surface: headers, hover states, inputs */
--border: #2a2a32 /* all borders */
--accent: #e8ff47 /* yellow — primary CTA, active state, playhead */
--accent2: #47c8ff /* blue — secondary CTA, server/network actions, focus */
--text: #e8e8ee /* body text */
--muted: #6b6b7a /* labels, metadata, placeholder text */
--waveform: #3a3a48 /* waveform bars */
--waveform-progress: #e8ff47 /* played waveform */
--waveform-hover: rgba(255, 255, 255, 0.85) /* waveform seek cursor */
/* Semantic alpha variants — use these instead of raw rgba() */
--accent-faint: rgba(232, 255, 71, 0.06) /* resting tint bg */
--accent-subtle: rgba(232, 255, 71, 0.08) /* hover / active tint bg */
--accent-border: rgba(232, 255, 71, 0.35) /* resting accent border */
--accent2-faint: rgba(71, 200, 255, 0.10) /* resting/hover accent2 tint bg */
--accent2-border: rgba(71, 200, 255, 0.25) /* resting accent2 border */
/* Status colors */
--danger: #ff7777 /* error, destructive */
--success: #47ff8a /* connected, success */
/* Typography */
--font-mono: 'IBM Plex Mono', monospace
The light theme (applied via [data-theme="light"] or @media (prefers-color-scheme: light) when [data-theme="auto"]) redefines all of these. The semantic roles remain the same but values shift (e.g. --accent becomes #7a7800, --accent2 becomes #0077cc). Always reference variables — the light overrides are transparent when you do.
Raw values still used directly (no variable):
- Warning/recording:
#ff4444 - Strong accent hover bg:
rgba(232, 255, 71, 0.12–0.18)(one-off, not worth a variable) - Strong accent hover border:
rgba(232, 255, 71, 0.6–0.7) - White overlay hover:
rgba(255, 255, 255, 0.05–0.08)
Typography
Only two fonts are used:
font-family: var(--font-mono); /* used for almost everything */
font-family: 'IBM Plex Sans', sans-serif; /* body/app shell only */
Use var(--font-mono) everywhere except the body element's base rule.
Type scale (all in rem):
| Role | Size | Weight | Letter-spacing | Transform |
|---|---|---|---|---|
| Page title / project title | 1.1rem | 600 | 0.08–0.15em | uppercase |
| Panel heading (h2) | 0.7rem | 600 | 0.12em | uppercase |
| Section label / sidebar title | 0.55–0.65rem | 600 | 0.10–0.12em | uppercase |
| Body / transcript text | 0.875rem | — | — | — |
| Track name | 0.78rem | 500 | 0.04em | — |
| Timecode | 0.85rem | 500 | 0.10em | — |
| Buttons / labels / meta | 0.6–0.65rem | — | 0.06–0.08em | — |
| Small badges / kbd keys | 0.48–0.55rem | — | 0.04–0.05em | — |
| Tiny section headers | 0.5rem | — | 0.1em | uppercase |
Surfaces & Panels
Every panel follows this layered pattern:
--bg (page)
└── --surface (panel/card) border: 1px solid var(--border); border-radius: 4px;
└── --surface2 (panel header, hover bg, inputs)
Panel header pattern:
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem–0.85rem 1.25rem;
border-bottom: 1px solid var(--border);
background: var(--surface2);
flex-shrink: 0;
}
.panel-header h2 {
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
}
Scrollable panel body:
.panel-body {
flex: 1;
overflow-y: auto;
padding: 1.25rem;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
.panel-body::-webkit-scrollbar { width: 3–4px; }
.panel-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
Overlay / modal pattern:
.overlay {
position: fixed;
inset: 0;
z-index: 4000;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
}
.modal {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.7);
font-family: var(--font-mono);
}
Popup / popover pattern (smaller floating panels):
border-radius: 6px,box-shadow: 0 6px 24px rgba(0,0,0,0.5–0.6)- Same
background: var(--surface),border: 1px solid var(--border)
Buttons
Standard button (.btn)
.btn {
background: none;
border: 1px solid var(--border);
color: var(--text);
border-radius: 2px;
cursor: pointer;
transition: all 0.15s;
font-family: var(--font-mono);
font-size: 0.65rem;
letter-spacing: 0.08em;
display: flex;
align-items: center;
gap: 0.35rem;
}
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn:active { background: var(--accent-subtle); }
.btn-sm { padding: 0.4rem 0.65rem; height: 30px; }
.btn-active { border-color: var(--accent) !important; color: var(--accent) !important; background: var(--accent-subtle); }
Accent (yellow) confirm button
background: var(--accent-subtle);
border: 1px solid var(--accent-border);
color: var(--accent);
/* hover: */ background: rgba(232, 255, 71, 0.18); border-color: rgba(232, 255, 71, 0.7);
Secondary (blue) action button
border: 1px solid var(--border);
background: transparent;
color: var(--accent2);
/* hover: */ background: var(--accent2-faint); border-color: var(--accent2);
Cancel / ghost button
background: transparent;
border: 1px solid var(--border);
color: var(--muted);
/* hover: */ background: var(--surface2); color: var(--text);
Destructive button
color: #ff9999;
border-color: rgba(255, 153, 153, 0.3);
/* hover: */ background: rgba(255, 119, 119, 0.1); border-color: var(--danger);
Play button (circular)
width: 42px; height: 42px;
border-radius: 50%;
border: 1.5px solid var(--accent);
color: var(--accent);
/* hover: */ background: rgba(232, 255, 71, 0.1); box-shadow: 0 0 16px rgba(232, 255, 71, 0.2);
All buttons: font-family: var(--font-mono), border-radius: 2–3px, transition: all 0.15s, cursor: pointer
Inputs
Text input
font-family: var(--font-mono);
font-size: 0.65rem;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 3px;
color: var(--text);
padding: 0.3rem 0.5rem;
outline: none;
transition: border-color 0.15s;
/* focus: */ border-color: var(--accent2);
/* placeholder: */ color: var(--muted); opacity: 0.6;
Inline editable text (click-to-edit pattern)
- Display state:
cursor: text, hover showsbackground: rgba(255,255,255,0.05),border-radius: 2–3px, negative margin trick to keep layout stable - Edit state:
border-bottom: 1.5px solid var(--accent),background: transparent,border: none(sides),outline: none
Range input
-webkit-appearance: none;
height: 2px;
background: var(--border);
border-radius: 1px;
/* thumb: */ width: 10px; height: 10px; border-radius: 50%; background: var(--accent);
Select
background: none;
border: 1px solid var(--border);
color: var(--muted);
font-family: var(--font-mono);
font-size: 0.65rem;
letter-spacing: 0.08em;
padding: 0.35rem 0.5rem;
border-radius: 2px;
height: 30px;
/* hover: */ border-color: var(--accent2); color: var(--accent2);
Badges & Tags
State badge (inline)
font-family: var(--font-mono);
font-size: 0.48–0.58rem;
letter-spacing: 0.05–0.06em;
padding: 2px 6px;
border-radius: 2–3px;
border: 1px solid;
white-space: nowrap;
Variants:
- local:
color: var(--muted); border-color: rgba(107,107,122,0.3); - server:
color: var(--accent2); border-color: rgba(71,200,255,0.3); background: rgba(71,200,255,0.06); - modified:
color: var(--accent); border-color: rgba(232,255,71,0.3); background: rgba(232,255,71,0.06);
<kbd> key display
background: rgba(255, 255, 255, 0.06);
border: 1px solid var(--border);
border-radius: 3px;
padding: 1px 4px;
color: var(--accent2);
font-family: var(--font-mono);
font-size: 0.55rem;
Color swatch dot
width: 8–10px; height: 8–10px;
border-radius: 50%;
flex-shrink: 0;
List / Table Items
Clickable list item
display: flex;
align-items: center;
padding: 0.45–0.65rem 1–1.25rem;
border-bottom: 1px solid rgba(42, 42, 50, 0.5);
cursor: pointer;
transition: background 0.1s;
font-family: var(--font-mono);
/* hover: */ background: var(--surface2);
/* active: */ background: var(--accent-faint); border-left: 2px solid var(--accent);
Table
border-collapse: collapse;
/* th: */ font-size: 0.6rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
color: var(--muted); padding: 0.5rem 1.25rem; border-bottom: 1px solid var(--border);
background: var(--surface); position: sticky; top: 0;
/* td: */ padding: 0.55rem 1.25rem; font-size: 0.7rem; color: var(--text);
/* tr hover: */ background: var(--surface2);
/* tr border: */ border-bottom: 1px solid rgba(42, 42, 50, 0.6);
Reveal-on-hover action buttons (delete, push, etc.)
opacity: 0;
transition: opacity 0.1s;
/* parent:hover > this: */ opacity: 1;
/* destructive hover: */ color: var(--danger);
Status Indicators
Status dot
width: 6px; height: 6px;
border-radius: 50%;
background: var(--muted); /* default/unknown */
/* .connected: */ background: var(--success);
/* .connecting: */ background: var(--accent); animation: pulse 1s ease-in-out infinite;
/* .error: */ background: var(--danger);
Pulse animation
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
Loading bar
height: 2px;
background: var(--border);
overflow: hidden;
/* ::after: */ background: var(--accent); width: 40%; animation: loading 1s ease-in-out infinite;
@keyframes loading { 0% { left: -40%; } 100% { left: 100%; } }
Interaction & Transition Conventions
| Situation | Value |
|---|---|
| Background / border color change | transition: background 0.1s or all 0.15s |
| Opacity reveal | transition: opacity 0.1s or 0.2s |
| Expand/collapse (sidebar, chevron) | transition: width/transform 0.2s ease |
| Subtle scale on hover (swatch) | transform: scale(1.3); transition: transform 0.1s |
| Box shadow on play button | 0 0 16px rgba(232, 255, 71, 0.2) |
Empty States
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 0.75rem;
color: var(--muted);
font-family: var(--font-mono);
font-size: 0.7rem;
letter-spacing: 0.06em;
text-align: center;
opacity: 0.5;
Z-Index Stack
| Layer | z-index |
|---|---|
| Hue picker popover | 1000 |
| Context menu | 2000 |
| Split popup | 3000 |
| Confirm dialog overlay | 4000 |
Do / Don't
Do:
- Use
var(--*)for all colors, never hardcode palette colors - Use
var(--font-mono)for all UI chrome;'IBM Plex Sans', sans-seriffor the body element only - Use the named alpha variables (
--accent-faint,--accent-subtle,--accent-border,--accent2-faint,--accent2-border) instead of rawrgba()where they apply - Use
--dangerand--successfor status colors instead of#ff7777/#47ff8a - Keep border-radius small:
2pxfor inline elements,3–4pxfor cards,5–6pxfor floating panels/modals - Use low-opacity tint backgrounds for active/selected states, never solid fills
- Use
opacity: 0+transitionfor reveal-on-hover actions (notdisplay: none) var(--danger)only appears on hover, never as a resting state color
Don't:
- Don't use saturated or bright backgrounds — all surfaces are near-black
- Don't use
font-weightother than 500 or 600 for Mono (regular is 400, only use 500/600 for emphasis) - Don't use
box-shadowfor depth except on floating overlays/popovers - Don't use color for decorative purposes — color encodes state (accent=active, accent2=server, danger=error/destructive, success=connected)
- Don't hardcode
rgba(232, 255, 71, …)orrgba(71, 200, 255, …)when a named alpha variable covers the value