Integrity Console Design System

Integrity Console Design System

The support dashboard rebuilt as a component library: 40 components, 31 stories, and a token layer the clickthrough and the React build both speak.

40
Components in the library
31
Stories browsable in Storybook
12
Full page compositions
Role
Staff Designer
Type
Design System
  • React
  • Storybook
  • Tailwind
  • Base UI
  • Design tokens

Problem

A clickthrough prototype proves an interaction and then stops being useful. The Integrity Console existed as 25 hand-built screens of vanilla HTML: convincing to click through, impossible to hand to an engineer as anything but a picture.

Task

Turn the prototype's decisions into a component library an engineer could build from: every surface, state and colour named once, and every component demonstrable in isolation.

Tokens
One name per surface, state and colour
Primitives
Button, badge, input, table
Surfaces
Inbox, dashboard, reports
Compositions
12, assembled at real scale

Two artefacts, one product

The Support Agent Dashboard shipped as a 25-screen clickthrough: vanilla HTML, CSS and JavaScript, no framework, no build step. Right shape for proving an interaction, wrong shape for handing to engineering, since a prototype has no components in it. It has markup that happens to look like components.

This is the other half: the same product expressed as a library.

The system

The library in Storybook, component by component.

Base UI primitives, Tailwind v4, shadcn conventions, typeset in Geist. 40 components across four groups, plus 12 page compositions that assemble them at real scale.

GroupCountWhat's in it
ui/21Accordion, avatar, badge, button, card, chart, command, dialog, dropdown, input, input group, popover, segmented control, skeleton, sonner, switch, table, tabs, textarea, toggle, toggle group
inbox/10Conversation list and rows, message thread, composer, chat header, side panel, role avatar and badge, command palette, close-conversation dialogs
dashboard/5Stat tile, trend chart, source card, compare toggle, report metric layout
chrome/4App shell, icon rail, secondary nav, topbar

Primitives, at the token level

UI / Button: all variants

Full screen

UI / Badge: all variants

Full screen

Two reds, one meaning each. --primary (#c70036) carries every primary action. --red-deep (#b20030) is reserved for the inbox Close button and the live-attempt badge, and nothing else. A single --red would have blurred two meanings into one swatch, and they'd have drifted apart in code anyway.

Inbox

Pages / Inbox

Full screen
The flagship surface: conversation list, thread, and context panel, built from the same components as everything else in the library.

One elevation, everywhere. The content card is the only shadowed surface on a page; nested blocks get --surface-nested and a hairline border, never a second shadow.

Dashboard and reports

Dashboard / StatTile: grid

Full screen

Pages / Reports Overview

Full screen

Role colour is semantic, not decorative. Student, instructor and admin each own a background/foreground pair, and the same three hues carry the chart series: an orange line on the Overview chart means the same thing an orange badge means in the Inbox.

The token layer is the actual deliverable

The prototype already had a disciplined tokens.css: surfaces, chat row states, a series palette for charts. Rather than redraw those decisions in Tailwind's vocabulary, the rebuild promotes them into a @theme layer under the same names, so a value never means one thing in the clickthrough and another in the React build.

src/index.css
:root {
  --primary: #c70036; /* the one red that means "primary action" */
  --background: #efeceb; /* app ground: cards float on it, no dividers */
  --surface-nested: #f8f7f6; /* tiles and chart wells inside a white card */
  --sel: #dbd3d1; /* selected chip, filter pill, received bubble */
 
  /* Inbox rows carry their own scale, because "unread" has to read as
     lighter than the ground while "read" reads as a step darker. */
  --unread: #fdfcfc;
  --chat-read: #e6e1e0;
}

Role pairs

Student, instructor and admin each own a background/foreground pair. The chart series reuse the same hues, so a colour carries one meaning across the whole product rather than one meaning per view.

RoleBackgroundForeground
Student#fae0d1#b94513
Instructor#dbeafe#1d4ed8
Admin#feeeaf#d97706

Status

TokenValueUse
--success#047857Resolved, healthy, within threshold
--warning#b45309Approaching a threshold
--destructive#e7000bFailed check, above threshold
--readonly#778ba6Fields the agent can see but not change

Inbox row states

The one place the palette gets fussy, because three states have to be distinguishable at a glance down a long list. Unread reads lighter than the ground; read reads a step darker; selected gets its own warm grey rather than borrowing --sel.

StateRestHover
Unread#fdfcfc#f4f0ef
Read#e6e1e0#dcd6d4
Selected#d2c8c6#c7bab7

Type

Geist Variable throughout: --font-heading is an alias of --font-sans, not a second family. Headings differentiate by weight and size against Tailwind's default scale; there is no bespoke scale to memorise.

Radius

One base, everything else derived. Changing --radius reshapes the product.

--radius: 0.75rem; /* lg */
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);

Elevation

--shadow-card: 0 0 4px rgba(0, 0, 0, 0.15);

That is the entire elevation system. The content card is the only shadowed surface on a page; anything nested inside it uses --surface-nested with a hairline border. A second shadow is a bug.

What I'd do next

  • Wire Code Connect so the Figma components point at these implementations rather than at a description of them.
  • Publish the report views as a template, since seven of the eight are the same layout with a different metric.