Design System
Components and patterns for the Cosmo pipeline orchestration tool. Built for power users: dense, warm, purposeful. Brand identity and token foundations are in the reference pages below.
Buttons
Primary action element. Three styles (primary / secondary / subtle) for different visual emphasis levels.
| Class | Purpose |
|---|---|
| .fbtn | Base, combine with a type and optionally a layout |
| .fbtn--primary | Accent fill, white text (Figma Primary) |
| .fbtn--secondary | Transparent fill, line border, ink text (Figma Secondary) |
| .fbtn--subtle | bg-panel fill, ink text (Figma Subtle) |
| .fbtn--success | Green-500 fill, white text — Approve / positive completion actions |
| .fbtn--icon-only | Square 32x32, no horizontal padding |
| .fbtn--sm | 24px height variant for history events and inline toolbars |
| .fbtn-chev | Trailing 12x12 chevron icon for dropdown buttons |
| State: hover | Pseudo-class, automatic |
| State: disabled | disabled attribute or .is-disabled |
<button class="fbtn fbtn--primary">
<i data-lucide="play"></i>Preview
</button>
<!-- Icon-only -->
<button class="fbtn fbtn--primary fbtn--icon-only">
<i data-lucide="play"></i>
</button>
<!-- With dropdown chevron -->
<button class="fbtn fbtn--secondary">
<i data-lucide="play"></i>Preview
<i data-lucide="chevron-down" class="fbtn-chev"></i>
</button>
| Dimension | Values |
|---|---|
| Type | --primary / --secondary / --subtle / --success |
| State | regular / hover / disabled |
| Layout | icon-only / icon+text / icon+text+chevron / text-only |
| Size | default 32px / --sm 24px |
Toggle
Two-state segmented switch for binary or short-list choices. Sits on a sunken pill; the selected tab lifts to panel-fill.
| Class | Purpose |
|---|---|
| .ftoggle | Container pill (bg-sunken), holds .ftoggle-tab children |
| .ftoggle--stretched | Modifier, tabs share width via flex:1 |
| .ftoggle-tab | Individual tab, 28px high, 8px radius, 12px medium text |
| .ftoggle-tab.is-selected | Active tab, bg-panel fill |
| .ftoggle-tab[disabled] | Disabled tab, cream-400 text |
| State: hover | Pseudo-class on non-selected, non-disabled tabs, bg-line fill |
<!-- Adaptive -->
<div class="ftoggle">
<button class="ftoggle-tab is-selected">
<i data-lucide="history"></i>History
</button>
<button class="ftoggle-tab">
<i data-lucide="messages-square"></i>Feedback
</button>
</div>
<!-- Stretched -->
<div class="ftoggle ftoggle--stretched">
<button class="ftoggle-tab is-selected">History</button>
<button class="ftoggle-tab">Feedback</button>
</div>
| Modifier | Effect |
|---|---|
ftoggle (default) | Adaptive — each tab sizes to its content |
ftoggle--stretched | Stretched — tabs share available width equally via flex:1 |
Data grid
Dense tabular layout for lists with consistent row structure. Header and rows share CSS subgrid columns so cells align across all rows.
max-content and container scrolls horizontally.- Column reorder via drag. Add
draggable="true"to.fgrid-th. Add.is-fixedon the identity column to opt out. While dragging, peers get.drop-target-leftor.drop-target-rightdepending on the cursor side, painting an inset border. - Column visibility menu. Open
.fgrid-col-menufrom a toolbar control. Render a checkbox per column in your registry; toggling re-renders the grid with the new column array. - Row selection. Add
.is-selectedto the row. Clicking again toggles off (your handler decides). Hover state lifts to--bg-panel; selected to--accent-wash. - 5-row max per group. Consumer-side: slice the data to 5 before rendering. No inner scroll on the lane — the page or outer container scrolls if multiple groups exceed viewport.
- Row separators. Drawn as
border-topon each row (header excluded), so the last row in a lane has no trailing line.
| Class | Purpose |
|---|---|
| .fgrid | Container CSS Grid. Set grid-template-columns inline; one slot per active column. Recommended floor + fill: minmax(max-content, 1fr). Container has min-width: max-content so it grows past parent for horizontal scroll when natural sum exceeds container. |
| .fgrid-head / .fgrid-row | Subgrid wrappers inheriting parent's column tracks via grid-template-columns: subgrid; grid-column: 1 / -1. Background + row separator live on the row, not per cell. |
| .fgrid-row:hover | Row hover, bg-panel fill across all cells. |
| .fgrid-row.is-selected | Selected row, accent-wash fill (matches active nav item). |
| .fgrid-th | Header cell. Add draggable="true" for column reorder. Add .is-fixed to opt out (identity column). |
| .fgrid-th.dragging | Drag-source state (set by handler, opacity 0.4). |
| .fgrid-th.drop-target-left / -right | Drop-target affordance painted as an inset accent border on the dragged-over peer. |
| .fgrid-cell | Data cell. Single-line ellipsis at max-width: 220px. |
| .fgrid-cell--name | Identity cell modifier. Opts out of ellipsis; hosts avatar + multi-line text + trailing action cluster. |
| .fgrid-col-menu | Column-control popover (toggle visibility from registry). |
| .fgrid-col-menu-item | One checkbox row inside the menu. |
<div class="fgrid" style="grid-template-columns: 220px minmax(max-content,1fr) minmax(max-content,1fr);">
<div class="fgrid-head">
<div class="fgrid-th is-fixed" data-col="name">Name</div>
<div class="fgrid-th" draggable="true" data-col="tier">Tier</div>
<div class="fgrid-th" draggable="true" data-col="status">Status</div>
</div>
<div class="fgrid-row">
<div class="fgrid-cell fgrid-cell--name">Coin Rain</div>
<div class="fgrid-cell"><span class="ftag ftag--tier-vip">VIP</span></div>
<div class="fgrid-cell"><span class="fstatus fstatus--approved">Approved</span></div>
</div>
</div>
Status
Lifecycle status of a playable. Closed enum — each status has a dedicated modifier so callers cannot mix mismatched colors.
| Class | Purpose |
|---|---|
| .fstatus | Base, combine with status modifier |
| .fstatus--not-started | grey |
| .fstatus--in-progress | blue |
| .fstatus--in-progress-soft | amber — Apollo orchestration in_progress state |
| .fstatus--pending-tl | purple, paired with hourglass icon |
| .fstatus--polish-tl | purple, paired with brush-cleaning icon |
| .fstatus--pending-pg | indigo, paired with hourglass icon |
| .fstatus--polish-pg | indigo, paired with brush-cleaning icon |
| .fstatus--approved | green |
| .fstatus--ready-to-send | green (alias of approved palette) |
| .fstatus--building | amber |
| .fstatus--build-failed | red |
| .fstatus--quota-backlog | grey (alias of not-started) |
<span class="fstatus fstatus--approved">Approved</span>
<!-- With icon (pending states) -->
<span class="fstatus fstatus--pending-tl">
<i data-lucide="hourglass"></i>TL
</span>
| Modifier | Color | Icon |
|---|---|---|
--not-started | grey | none |
--in-progress | blue | none |
--pending-tl | purple | hourglass |
--polish-tl | purple | brush-cleaning |
--pending-pg | indigo | hourglass |
--polish-pg | indigo | brush-cleaning |
--approved | green | none |
--ready-to-send | green | none |
--building | amber | none |
--in-progress-soft | amber | none (Apollo only) |
--build-failed | red | none |
--quota-backlog | grey | none |
Tag
Compact pill for tags, badges, and categorical labels. Three shape families: symbol square, text pill, and two-segment revision label.
| Class | Purpose |
|---|---|
| .ftag | Base, combine with a named modifier |
| .ftag--symbol | Square 20x20 shape modifier (icon-only or single letter) |
| .ftag--square | Uniform 20x20 square regardless of variant — for row leading tags (V / R1 / R2 / R3) |
| .ftag--version | Symbol — green, single "V" or check |
| .ftag--rolled-over | Symbol — orange, corner-down-right icon |
| .ftag--high-priority | Symbol — indigo, crown icon |
| .ftag--client-request | Symbol — purple, megaphone icon |
| .ftag--general | Text tag, sunken bg, muted text |
| .ftag--tier-t1 / -t2 / -t3 | Tier square 20x20 |
| .ftag--tier-vip / -fde | Tier pill (text) |
| .ftag--effort-easy / -medium / -hard | Effort pill, icon + label |
| .ftag--revision .ftag--revision-(regular|warning1|warning2) | Two-segment: .ftag-rev-label + .ftag-rev-age |
<!-- Text pill -->
<span class="ftag ftag--tier-vip">VIP</span>
<!-- Effort pill with icon -->
<span class="ftag ftag--effort-easy">
<i data-lucide="leaf"></i>Easy
</span>
<!-- Revision two-segment -->
<span class="ftag ftag--revision ftag--revision-regular">
<span class="ftag-rev-label">R3</span>
<span class="ftag-rev-age">3h</span>
</span>
| Family | Variants |
|---|---|
| Symbol square | --version / --rolled-over / --high-priority / --client-request |
| Text pill | --general / --tier-vip / --tier-fde / --tier-t1/t2/t3 / --effort-easy/medium/hard |
| Two-segment revision | --revision-regular / --revision-warning1 / --revision-warning2 |
| Shape override | --square — forces 20x20 uniform square for row-leading tags |
Inputs
All form controls share the same border token and focus ring. Text inputs, selects, checkboxes, radio buttons, and switches — one consistent system for every user interaction that collects a value.
Text input
Select & checkboxes
Checkbox
Native <input type="checkbox"> styled with accent-color: var(--accent). Always paired with a label via .checkbox-row.
Radio
Native <input type="radio"> styled with accent-color: var(--accent). Group via shared name attribute.
Checkbox
Bordered checkbox with accent fill on checked state. Use as a <button role="checkbox"> with .checkbox--on modifier.
Check
Borderless checkmark toggle. No background or border in either state. The checkmark appears in accent on checked. Use in dense table row contexts where a bordered box would be visually noisy.
Switch
40x24 pill toggle. Track transitions cream-200 to accent. Knob is 18x18, cream-50, transitions from left:3 to left:19. Use .switch--on modifier.
| Class | Description |
|---|---|
| .field | Flex-column wrapper for label + input + hint |
| .field-label / .field-hint | Above-field label (550 weight) and optional clarifying hint |
| .input | Text input — bg-raised, accent focus ring |
| .input.input-error | Error state — red border and focus shadow |
| .select | Dropdown — same sizing as .input, custom chevron via background-image |
| .checkbox-row | Flex-row wrapper for checkbox / radio / switch + label |
| button.checkbox | 16x16 bordered checkbox. Off: transparent + cream border. On: add .checkbox--on (accent fill, embed check SVG). |
| .checkbox--on | Checked state modifier. Accent background + cream-50 icon color. Embed check SVG as child. |
| .check | 16x16 borderless checkmark toggle. No border or fill in either state. On: add .check--on (accent icon color). |
| .check--on | Checked state modifier. Sets color to accent so the inline SVG resolves via currentColor. |
| .switch | 40x24 pill toggle. Off: cream-200 track. On: add .switch--on (accent track). Always contains <span class="switch-knob">. |
| .switch--on | Checked state modifier. Track turns accent, knob slides from left:3 to left:19. |
| .switch-knob | 18x18 circle knob inside .switch. Positioned absolute, transitions on left. |
Text input
<div class="field">
<label class="field-label">Search</label>
<input class="input" type="text" placeholder="Search playables…">
</div>
<!-- Error state -->
<div class="field">
<label class="field-label">Playable ID</label>
<input class="input input-error" type="text" value="PLY-abc">
<span style="font-size:var(--text-xs);color:var(--red-600)">Invalid format</span>
</div>
Select
<div class="field">
<label class="field-label">Status filter</label>
<select class="select">
<option>All statuses</option>
<option>On track</option>
<option>At risk</option>
</select>
</div>
Checkbox
<!-- Native checkbox (accent-color styled) -->
<label class="checkbox-row">
<input type="checkbox" class="checkbox" checked>
<span class="checkbox-label">Include revisions</span>
</label>
<!-- Button checkbox (Figma-spec bordered variant) -->
<button class="checkbox checkbox--on" type="button" role="checkbox" aria-checked="true">
<svg width="10.5" height="8" viewBox="0 0 10.5 8" fill="none" aria-hidden="true">
<path d="M9.5 1L3.5 7L1 4.5" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
Radio
<label class="checkbox-row">
<input type="radio" name="filter" class="checkbox" checked>
All playables
</label>
<label class="checkbox-row">
<input type="radio" name="filter" class="checkbox">
Only ready to send
</label>
Switch
<!-- Off -->
<button class="switch" type="button" role="switch" aria-checked="false">
<span class="switch-knob"></span>
</button>
<!-- On -->
<button class="switch switch--on" type="button" role="switch" aria-checked="true">
<span class="switch-knob"></span>
</button>
Icons
Icons come from Lucide, a MIT-licensed stroke icon library embedded locally. Use <i data-lucide="icon-name"></i> — Lucide replaces the element with an inline SVG at runtime.
Navigation icons
Usage
| Usage | Description |
|---|---|
| <i data-lucide="icon-name"></i> | Replaced at runtime by lucide.createIcons(). Add CSS width/height and stroke properties to size and color. |
| .nav-icon | 14×14 with stroke-width 1.8. Inherits opacity from parent nav-item state. |
| npm run update-icons | Run from data/ to pull latest Lucide and copy the UMD bundle to lib/lucide.min.js. |
<!-- Standard usage -->
<i data-lucide="calendar-days" style="width:20px;height:20px;stroke:var(--ink-soft);stroke-width:1.8"></i>
<!-- Nav icon (14x14, 1.8 stroke) -->
<i data-lucide="send" class="nav-icon"></i>
Data visualization
One home for every quantitative surface: KPI tiles for headline metrics, the distribution bar for live pipeline filtering, load bars for auditor capacity, and Recharts for trend, proportion, and gauge views. All use Cosmo tokens.
KPI tile — states
Three meaningful states: positive delta, negative delta (uses .kpi-delta.down), and zero-value (uses .kpi-delta.flat). The .accent modifier tints the value to orange for primary-metric emphasis.
Compact variant (.kpi.sm)
Tighter padding, smaller font, smaller min-width. Used in dense sub-header strips (Pulse, Delivery) where full KPI tiles would dominate.
Status distribution bar (.sdb)
Horizontal pipeline bar — each segment is one stage_status, width proportional to count. Click a segment to filter (others dim); click again or the × clear button to reset. Labels collapse to icon-only when there isn't room. Canonical JS pattern lives in screens/prototypes/air.html.
Load bars — auditor capacity (.load-bar-wrap)
Compact, named bars for "auditor X is at Y/N capacity". Severity-colored fill: .low (green) / .high (amber, ≥75%) / .over (red, ≥100%). Use these for compact summary surfaces; for the in-lane capacity row inside .kanban-lane use .cap-bar instead (Patterns · 05 · Kanban lane).
Charts — Recharts
All charts use Recharts (React) styled with Cosmo tokens — green = on-track, amber = at-risk, red = behind, accent = highlight. No default Recharts tooltip/legend styling; everything is overridden in the React mount script at the bottom of this file. For HTML prototypes outside React, use the same approach via the Recharts UMD build.
| Class / Element | Description |
|---|---|
| KPI tiles | |
| .kpi / .kpi.sm | KPI tile — flex column with label, value, delta. .sm is the compact variant. |
| .kpi.accent | Tints .kpi-value to the accent color for primary-metric emphasis. |
| .kpi-label / .kpi-value / .kpi-delta | Mono uppercase label / display numeral / mono trend indicator. |
| .kpi-delta.down / .flat | Red (down) or muted (flat) delta. Default is green. |
| Status distribution bar | |
| .sdb | Bar container — flex row, 22px height, radius-full, overflow hidden. |
| .sdb-header / .sdb-label / .sdb-filter-hint / .sdb-clear-btn | Header row above the bar — label left, filter hint + clear button right. |
| .sdb-seg | Segment. Width inline as %, background inline from STATUS_CFG. |
| .sdb-seg.is-active / .is-dimmed / .is-wide / .is-light | Filter active / others dimmed / wide enough to show label / dark-bg variant (cream text). |
| .sdb-seg-icon / .sdb-seg-label | 10×10 Lucide wrapper / mono label hidden until .is-wide. |
| .sdb-tooltip | Body-attached fixed tooltip, JS-positioned. Add .is-visible to show. |
| .dist-bar / .dist-seg | Legacy aliases — same visual output. Prefer .sdb / .sdb-seg. |
| Load bars | |
| .load-bar-wrap / .load-bar-track / .load-bar-fill | Compact severity-colored capacity bar. .low (green) / .high (amber, ≥75%) / .over (red, ≥100%). |
| .load-count | Mono "X / N" count next to the bar. |
| Charts | |
| Delivery Trend | AreaChart + Area (fill gradient) + Line (target dashed) + axes + Tooltip + CartesianGrid |
| Status by Game | BarChart + Bar × 3 (stacked) + axes + Tooltip + Legend |
| Status Donut | PieChart + Pie (innerRadius 60, outerRadius 90) + Cell per segment + Legend |
| Team Capacity | PieChart + Pie (semicircle, startAngle=180, endAngle=0) + center label via foreignObject |
| Auditor Workload | BarChart (layout=vertical) + Bar × 2 (active + remaining) + axes + Tooltip |
| Color palette | green-500 (on-track), amber-600 (at-risk), red-500 (behind), orange-500 (accent / delivered), ink-300 (grid), ink-400 (axis text) |
KPI tile
<div class="kpi accent">
<div class="kpi-label">Delivered this week</div>
<div class="kpi-value">38 <span class="unit">/ wk</span></div>
<div class="kpi-delta">↑ +6 vs last week</div>
</div>
<!-- States: .kpi-delta.down (red), .kpi-delta.flat (muted) -->
<!-- Compact: add .sm modifier to .kpi -->
Status distribution bar
<div class="sdb-header">
<span class="sdb-label">Pipeline</span>
<span class="sdb-filter-hint">
<span id="hint-text"></span>
<button class="sdb-clear-btn">× clear</button>
</span>
</div>
<div class="sdb" id="sdb-bar">
<!-- Segments injected by JS. Each: -->
<!-- <button class="sdb-seg" style="width:15%;background:var(--green-500)"
data-tip="Build Complete">...</button> -->
</div>
Load bar
<div class="load-bar-wrap">
<div class="load-bar-track">
<div class="load-bar-fill high" style="width:82%"></div>
</div>
<div class="load-count">9 / 11</div>
</div>
<!-- Severity modifiers: .low (green), .high (amber ≥75%), .over (red ≥100%) -->
Charts (Recharts UMD)
<!-- Mount target -->
<div id="chart-delivery" style="height:220px;"></div>
<!-- React/Recharts mount (see bottom of index.html for full script) -->
<!-- Colors: green-500, amber-600, red-500, orange-500 — all via DS tokens -->
Kanban lane
Vertical column for an auditor or status bucket. Header holds the avatar, name, and an optional two-row layout for capacity. The scrollable body holds playable cards and activates a drop-zone highlight during drag.
Lanes — default & active drop-zone
Two-row head — name + role badge + capacity bar
Used by weekly v2 to make room for a capacity bar below the avatar/name row. .kl-head-row1 is the avatar + name + days chip. .kl-head-name-group wraps the name and an adjacent role badge (e.g. "TL") so they stay together with predictable ellipsis. .kl-cap-row is the capacity bar row.
Capacity controls — day chip + popover
The .kl-days-chip in the lane header opens a .kl-days-pop popover with a stepper and a vacation toggle. The popover is position:fixed so it can escape the lane's overflow:hidden; the consumer positions it via getBoundingClientRect. Below the chip is the popover open inline so its contents are visible in this docs page.
On-vacation lane state
Add .on-vacation to .kanban-lane to fade the body, hatch it diagonally, hide the capacity row, and surface the amber .kl-vac-banner between the head and the body.
Team divider — between adjacent lanes
Insert .kl-team-divider between two lanes when they belong to different teams (e.g. multi-TL view). 1px ink line, full lane height, small horizontal margins.
| Class | State / Variant | Description |
|---|---|---|
| .kanban-lane | default | Vertical column shell — min-width 280px, panel surface, rounded corners, vertical flex |
| .kanban-lane.on-vacation | state | Fades body, applies hatched stripe overlay, hides cap row, exposes .kl-vac-banner |
| .kl-head | structure | Lane header — padded, bottom border. Flex-direction can be overridden for two-row layouts |
| .kl-head-row1 | structure | First row of a two-row head — avatar + name-group + days chip |
| .kl-head-name | element | Auditor name — UI font, 600, ellipsis |
| .kl-head-name-group | element | Wraps name + role badge so they stay together with predictable ellipsis |
| .kl-head-count DEPRECATED | — | Numeric pill for card count. Replaced by .priority-slot. Kept for back-compat only. |
| .kl-cap-row | element | Capacity bar + readout row inside the lane head |
| .kl-body | structure | Scrollable container for cards — gap 8px, padding 12px |
| .kl-body.drop-zone | state | Active drag-drop target — accent-wash background and inset accent ring |
| .kl-team-divider | element | 1px vertical line inserted between lanes from different teams |
| .kl-vac-banner | element | Amber strip shown between head and body when lane .on-vacation |
| .kl-days-chip | default | Mono pill showing weekly day capacity — click to open .kl-days-pop |
| .kl-days-chip.on-vacation | state | Amber "Vac" variant when the lane is on vacation |
| .kl-days-pop / .open | default / state | Day-capacity popover. Add .open to display |
| .kl-days-stepper-btn / :disabled | element / state | 24×24 ± button. Disabled at boundaries (0d / 7d) and during vacation |
| .kl-days-switch / .on | default / state | 26×14 pill toggle. .on applies accent color |
<!-- Default lane -->
<div class="kanban-lane">
<div class="kl-head">
<div class="avatar">TI</div>
<span class="kl-head-name">Tatiana I.</span>
</div>
<div class="kl-body">
<div class="ply-card">...</div>
</div>
</div>
<!-- On-vacation state -->
<div class="kanban-lane on-vacation">
<div class="kl-head">...</div>
<div class="kl-vac-banner"><i data-lucide="palmtree"></i> On vacation</div>
<div class="kl-body">...</div>
</div>
<!-- Two-row head with capacity bar -->
<div class="kl-head">
<div class="kl-head-row1">
<div class="avatar">TI</div>
<div class="kl-head-name-group">
<span class="kl-head-name">Tatiana I.</span>
</div>
<div class="kl-days-wrap"><span class="kl-days-chip">5d</span></div>
</div>
<div class="kl-cap-row">
<div class="cap-bar"><div class="cap-bar-track"><div class="cap-bar-fill" style="width:60%"></div></div></div>
<span class="cap-bar-nums">3/5</span>
</div>
</div>
| Variant | Modifier |
|---|---|
| Default | .kanban-lane |
| On vacation | .kanban-lane.on-vacation + .kl-vac-banner |
| Two-row head | .kl-head-row1 + .kl-cap-row inside .kl-head |
Drag-and-drop states
Three utility classes applied via JS during the drag lifecycle: the card being dragged, the hovered drop target, and a passive container that signals it can receive the active drag.
Source — .dragging
Applied to the card being dragged — half opacity, grabbing cursor.
Hovered target — .drop-target
Applied to the element the dragged card is currently over.
Active container — .drop-zone-active
Applied to a wider container (e.g. an empty area of a kanban lane body) so the user can see where a drop is allowed.
| Class | Description |
|---|---|
| .dragging | Apply to source card during drag — opacity 0.5, cursor: grabbing. |
| .drop-target | Apply to hovered target — accent border, accent-wash background. Uses !important to win over any baseline border colors. |
| .drop-zone-active | Apply to a container that can receive the active drag — dashed accent outline, accent-wash fill. |
<!-- Source card being dragged -->
<div class="ply-card dragging">...</div>
<!-- Hovered drop target -->
<div class="ply-card drop-target">...</div>
<!-- Container that can receive the drag -->
<div class="kl-body drop-zone-active">...</div>
| Variant | Class |
|---|---|
| Dragging (source) | .dragging |
| Drop target (hovered) | .drop-target |
| Drop zone (active container) | .drop-zone-active |
Team picker
Workspace-scope selector used in the Weekly app topbar to switch between auditor teams. Small bordered trigger with a users icon, current label, and a chevron that rotates 180° on open. Pairs with .popover-menu.
Closed and open
| Class | Description |
|---|---|
| .team-picker | Wrapper — relative positioning for the menu |
| .team-picker-trigger | Bordered button — 6px / 10px padding, line border, hover sunken. Drive open state with aria-expanded="true" |
| .team-picker-icon | 14px leading icon — ink-muted, stroke-width 1.8 |
| .team-picker-chevron | 12px trailing chevron — rotates 180° when [aria-expanded="true"] |
| .team-picker .popover-menu | Menu inside the picker — widened to 220px so team names don't truncate |
<div class="team-picker">
<button class="team-picker-trigger" aria-expanded="false">
<i data-lucide="users" class="team-picker-icon"></i>
<span>Tatiana I.'s team</span>
<i data-lucide="chevron-down" class="team-picker-chevron"></i>
</button>
<!-- Add .popover-menu here when open (aria-expanded="true") -->
</div>
Priority slot
Wrapper that prepends a CSS counter number to a card. The number sits in an 18px left gutter; the card itself stays untouched. Used to show ranked priority order in lanes, where top position equals priority 1.
Numbered lane body
Set counter-reset: priority-rank; on the parent — here, an inline style on the wrapper.
Drop indicator
A 2px accent line shown between two cards to signal the exact insertion slot during drag-and-drop. Render as a sibling element inside the drop container.
| Class | Description |
|---|---|
| parent: counter-reset | Set counter-reset: priority-rank; on the parent list (e.g. the .kl-body inside a kanban-lane) |
| .priority-slot | Wrapper — counter-increment + 22px left padding. The card sits inside, untouched |
| .priority-slot::before | The number itself — mono 10px, ink-muted, in an 18px-wide centered gutter on the left |
| .drop-indicator | 2px accent line rendered as a sibling element inside the drop container while a drag is hovering |
<div style="counter-reset: priority-rank;">
<div class="priority-slot">
<div class="ply-card">...</div>
</div>
<div class="priority-slot">
<div class="ply-card">...</div>
</div>
<!-- Drop indicator between slots: -->
<div class="drop-indicator"></div>
<div class="priority-slot">
<div class="ply-card">...</div>
</div>
</div>
Scrollbar — thin
Utility class that swaps the chunky default scrollbar for a minimal 4px track and thumb matching DS surface tokens. Apply to any scrollable container. Webkit-only; Firefox falls back to its native thin styling.
Thin scrollbar in a panel
Resize-friendly preview with overflowing content so the scrollbar is visible.
| Class | Description |
|---|---|
| .scrollbar-thin | Apply to a scrollable container — 4px webkit scrollbar with a transparent track and a line-strong thumb (ink-300 on hover) |
<div class="scrollbar-thin" style="height:200px;overflow-y:auto;">
<!-- scrollable content -->
</div>
Floating panel
Elevated side-panel shell at 280px fixed width. The canonical use is the Quota Backlog panel in the Weekly dispatch screen. Add .hidden to animate the panel to zero width with no content jump.
Default (with generic content skeleton)
280px wide, shadow-md. Consumers add any header + scrollable body inside — the panel enforces only the outer shell.
States
| Class | State / Variant | Description |
|---|---|---|
| .floating-panel | default | 280px flex column — bg-panel surface, 1px line border, 14px radius, shadow-md. Width fixed; add overflow:hidden on the element. |
| .floating-panel.hidden | state | Collapses to width:0 / min-width:0 — animate with transition on width. Content hidden by overflow:hidden. |
| .qb-panel | alias | Identical to .floating-panel. Kept live for weekly.html back-compat. New code uses .floating-panel. |
| .qb-panel.hidden | alias state | Same collapse behavior as .floating-panel.hidden. |
<!-- Default -->
<div class="floating-panel">
<div style="padding:var(--space-4);border-bottom:1px solid var(--line);
display:flex;align-items:center;justify-content:space-between;">
<span style="font-family:var(--font-display);font-weight:700;">Panel title</span>
<button class="icon-btn"><i data-lucide="x"></i></button>
</div>
<div class="scrollbar-thin" style="flex:1;overflow-y:auto;padding:var(--space-4);">
<!-- scrollable body -->
</div>
</div>
<!-- Collapsed -->
<div class="floating-panel hidden"></div>
| Variant | Modifier |
|---|---|
| Default (visible) | .floating-panel |
| Hidden (collapsed to 0) | .floating-panel.hidden |
Data table
Generic table chrome for Settings-style data tables. Covers person cell, avatar, role tags, status toggle, kebab menu, subsection grouping, orphan row, inline-editable input, and allocations matrix wrapper.
Basic table with person cell, role tags, switch, kebab
| User | Role | Active | Capacity | |
|---|---|---|---|---|
|
AK
Avi Katz
avi@sett.ai
|
TL | 8 |
|
|
|
NR
Noa Reiner
noa@sett.ai
|
PG | 6 |
|
|
|
MS
Maya Schwartz
maya@sett.ai
|
Auditor | 4 |
|
Subsection grouping (.dt-row--group)
| User | Role | Cap |
|---|---|---|
| Studio A | ||
AK Avi Katz |
TL | 8 |
| Studio B | ||
NR Noa Reiner |
PG | 6 |
Orphan row (.dt-row--orphan)
| User | Role | Cap |
|---|---|---|
AK Avi Katz |
TL | 8 |
?? UnassignedNo studio |
-- | 3 |
Inline-editable input (.dt-input-cell)
| User | W16 | W17 | W18 |
|---|---|---|---|
AK Avi Katz |
|||
NR Noa Reiner |
Empty state (.dt-empty)
| User | Role |
|---|---|
| No users in this section. | |
| Class | Element / State | Description |
|---|---|---|
| .dt-table | block | Full-width table with border, radius, bg-raised background. |
| .dt-table thead th | element | Mono 10px uppercase header, bg-panel, border-bottom. |
| .dt-table tbody tr | element | Row with border-bottom; hover state switches to bg-sunken. |
| .dt-cell--center / --right / --num | modifier | Centers / right-aligns / mono-centered cell. Use --num for numeric columns. |
| .dt-person | element | Flex row: avatar + .dt-person-info. min-width 180px. |
| .dt-avatar | element | 24x24 round mono-initials chip. |
| .dt-name / .dt-sub | element | font-ui 13px 500 primary label / mono 10px muted sub-label. |
| .dt-tag | element | Pill badge base (neutral). Role tags use canonical .tag family: TL = .tag.tag--orange, PG = .tag.tag--blue, Auditor = .tag.tag--green. |
| .dt-tag--tl / --pg / --auditor | deprecated | Removed from showcase 2026-05-10. Use .tag.tag--orange / .tag.tag--blue / .tag.tag--green. |
| .switch / .switch--on | block | 40x24 pill toggle. See Inputs section (Components · 07). |
| .dt-toggle / .dt-toggle-track / .dt-toggle-thumb | deprecated | Kept for existing screens. Use .switch for new work. |
| .dt-kebab-btn / .dt-kebab-menu / .open | element / state | Table-inline kebab. Absolute menu; add .open to show. |
| .dt-kebab-item / .danger | element | Full-width button row in menu. .danger sets red color. |
| .dt-row--group / .dt-group-label | modifier | Group header row with bg-panel + mono uppercase label. |
| .dt-row--orphan | modifier | Row with red left border bar (inset box-shadow on first td). |
| .dt-empty | element | Centered muted placeholder cell spanning full colspan. |
| .dt-input-cell | element | 52px mono number input. Transparent border default; line on hover, accent on focus. |
| .dt-matrix | modifier | Outer frame for 2D allocation grids. Pairs with .alc-matrix-wrap. |
<table class="dt-table">
<thead>
<tr><th>User</th><th>Role</th><th class="dt-cell--center">Active</th><th></th></tr>
</thead>
<tbody>
<tr>
<td>
<div class="dt-person">
<div class="dt-avatar">AK</div>
<div class="dt-person-info">
<span class="dt-name">Avi Katz</span>
<span class="dt-sub">avi@sett.ai</span>
</div>
</div>
</td>
<td><span class="tag tag--orange">TL</span></td>
<td class="dt-cell--center">
<button class="switch switch--on" role="switch" aria-checked="true">
<span class="switch-knob"></span>
</button>
</td>
<td style="width:32px;">
<div style="position:relative;">
<button class="dt-kebab-btn"><i data-lucide="more-vertical"></i></button>
<div class="dt-kebab-menu">
<button class="dt-kebab-item"><i data-lucide="edit-2"></i> Edit</button>
<button class="dt-kebab-item danger"><i data-lucide="trash-2"></i> Remove</button>
</div>
</div>
</td>
</tr>
</tbody>
</table>
| Variant | Modifier |
|---|---|
| Person cell | .dt-person + .dt-avatar + .dt-person-info |
| Subsection group | .dt-row--group + .dt-group-label |
| Orphan row | .dt-row--orphan |
| Empty state | .dt-empty on a colspan td |