Guides
Theming & styling
Three tiers of visual control - token overrides, className props, and source imports - from a quick re-skin to a fully custom component tree.
WorkerDeck’s UI is designed for embedding, and embedders need different levels of visual control. The package offers three tiers, from lightest to deepest:
- CSS token overrides - change colours, type, spacing, geometry with one block of CSS.
classNameanddata-slot- restyle any component or sub-element without forking.- Source imports - consume the raw
.tsxfiles as your own code.
Every tier composes: start with tokens, add targeted className overrides where tokens don’t
reach, and fall through to source only for the components you truly need to rewrite.
Tier 1: CSS custom property tokens
Every visual decision in @workerdeck/ui is driven by CSS custom properties. Override them on
.wd-root (scoped mode) or :root (full-page mode) and the entire UI follows.
Colour tokens (swap by [data-theme])
These are the ones most embedders override. Set them inside [data-theme='light'] and
[data-theme='dark'] blocks (or on .wd-root to pin one theme):
| Token | What it paints |
|---|---|
--bg |
Page / panel background |
--bg-surface |
Cards, elevated surfaces |
--bg-surface-hover |
Surface hover fill |
--bg-code |
Code block backgrounds |
--border, --border-light, --border-strong |
Border hierarchy |
--fg-1 through --fg-4 |
Text hierarchy (1 = primary, 4 = disabled) |
--accent |
Primary action colour (buttons, links, rings) |
--accent-hover, --accent-dim, --accent-bg |
Accent variants |
--accent-fg |
Text on accent fills |
--sidebar |
Sidebar / list background |
--row-hover, --row-active, --row-selected, --row-selected-weak |
List row states |
--success, --warning, --danger, --info |
Semantic colours |
--success-bg, --warning-bg, --danger-bg, --info-bg |
Semantic tint backgrounds |
--vendor-claude, --vendor-openai |
Engine brand marks |
Example - match your app’s brand:
.wd-root {
--accent: #6366f1; /* indigo primary */
--accent-hover: #4f46e5;
--accent-bg: #eef2ff;
--accent-fg: #ffffff;
--accent-ring: #6366f133;
}
[data-theme='dark'] .wd-root {
--accent-bg: #1e1b4b;
}
Typography tokens
| Token | Default | What it controls |
|---|---|---|
--cw-font-sans |
Inter, system-ui, … | UI typeface |
--cw-font-mono |
JetBrains Mono, ui-monospace, … | Code / terminal typeface |
--text-body |
0.9375rem (15px) | Base text size |
--text-body-sm |
0.8125rem (13px) | Secondary text |
--text-label |
0.75rem (12px) | Labels, metadata |
--text-code |
0.84375rem (~13.5px) | Code blocks |
Geometry tokens
These control layout dimensions embedders commonly want to adjust:
| Token | Default | What it sizes |
|---|---|---|
--wd-status-bar-height |
38px |
Height of the status bar |
--wd-composer-padding |
12px |
Padding around the composer area |
--wd-transcript-row-gap |
0px |
Gap between transcript rows |
--wd-transcript-max-width |
48rem |
Max width of transcript content column |
/* Compact status bar for a narrow dock */
.wd-root {
--wd-status-bar-height: 32px;
--wd-composer-padding: 8px;
}
Radii, motion, shadows
| Token | Default |
|---|---|
--radius-sm / --radius-md / --radius-lg |
4px / 6px / 8px |
--motion-fast / --motion-base / --motion-slow |
120ms / 150ms / 220ms |
--shadow-xs through --shadow-lg |
Elevation ramp (flat by default, shadows for overlays) |
Token stability
Tokens prefixed --wd- are public API and follow semver. The unprefixed colour and
typography tokens (--bg, --accent, --fg-1, --cw-font-sans, etc.) are stable in practice
and documented here as the theming surface, but new tokens may be added in minor releases.
Tier 2: className props and data-slot selectors
Every exported component accepts a className prop on its root element, merged via
tailwind-merge so your classes win ties. For sub-elements, every component emits a data-slot
attribute you can target with CSS.
className on the root
<SessionPanel
className='rounded-xl shadow-lg'
client={client}
sessionId={sessionId}
/>
<StatusBar
className='border-b-0 bg-transparent'
state={state}
/>
<ToolCallCard
className='border-none bg-surface'
item={item}
/>
data-slot selectors
Every component carries a data-slot attribute naming what it is. Use these as CSS selectors
to reach inside a composite without forking it:
/* Restyle tool cards inside the panel */
.wd-root [data-slot='tool-call'] {
border-radius: 12px;
border-color: var(--border-light);
}
/* Tighten the status bar's internal padding */
.wd-root [data-slot='status-bar'] {
padding: 4px 8px;
}
/* Restyle the composer */
.wd-root [data-slot='composer'] {
background: var(--bg-surface);
border-radius: 12px;
}
/* Change the session list item shape */
.wd-root [data-slot='session-item'] {
border-radius: 8px;
}
Available data-slot values
These are the stable slot names you can target:
Layout: session-panel, session-workspace, status-bar, status-line
Transcript: transcript-rows, transcript-hold, message, message-content, reasoning,
tool-call, file-delivered, loader, notice, recap, turn-result, brief, catch-up
Interaction: composer, permission-prompt, question-prompt
Session list: session-browser, session-list, session-list-item, session-item
Primitives: button, badge, card, input, textarea, select-trigger, select-content,
select-item, menu-content, menu-item, dialog-content, tooltip-content, code-block,
splitter, progress-ring
Tier 3: Source imports
The npm package ships src/ alongside build/, and package.json declares an
@workerdeck/source condition on every export. A bundler configured to resolve that condition
imports the raw TypeScript source:
// vite.config.ts (or equivalent)
{
resolve: {
conditions: ['@workerdeck/source']
}
}
With that condition active, import { SessionPanel } from '@workerdeck/ui' resolves to
packages/ui/src/index.ts - the actual .tsx files, compiled by your own build. You get:
- Full tree-shaking - only the components you use are compiled.
- Your own Tailwind build processes the classes, so overriding at the utility level works natively.
- You can copy and modify any component into your own tree, importing the rest from the
package. The internal seams (
cn, the format utilities, the transcript variant context) are all exported.
This is the escape hatch for when tokens and className are not enough: copy StatusBar.tsx
into your project, reshape it, and import everything else from the package.
The headless layer
If you want to build the entire UI from scratch, skip @workerdeck/ui entirely and build on
@workerdeck/react:
import { useClaudeSession, type TranscriptState } from '@workerdeck/react'
function MyPanel({ client, sessionId }) {
const { state, sendMessage, approve, deny, interrupt } = useClaudeSession(client, sessionId)
// state.items, state.status, state.model, state.permissionRequests, ...
// render however you want
}
The hook attaches, folds events through a pure reducer, and hands back live state plus the
control surface. The framework-free reducer (applyEvent, initialTranscriptState) is also
exported for non-React use.
Quick reference: which tier for what
| Goal | Tier |
|---|---|
| Match my app’s colour palette | 1 - override --accent, --bg, --fg-* |
| Use my own fonts | 1 - override --cw-font-sans, --cw-font-mono |
| Adjust spacing / sizing | 1 - override --wd-* geometry tokens |
| Restyle one component’s root | 2 - className prop |
| Restyle a sub-element (e.g. tool card header) | 2 - [data-slot] CSS selector |
| Reshape a component’s structure | 3 - source import, copy, modify |
| Build a fully custom UI | 3 - @workerdeck/react headless hook |