27 KiB
name, description, colors, typography, rounded, spacing, components
| name | description | colors | typography | rounded | spacing | components | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Superbill Extraction and Automation Tool | Extract billing fields from a scan, verify them against the page, post to eClinicalWorks. |
|
|
|
|
|
Design System: Superbill Review
Overview
Creative North Star: "The Triage Desk"
Superbill Review reads like an emergency triage desk, not a spreadsheet. Every extracted field arrives already sorted by how urgently it needs attention — green passes through untouched, amber gets a spot-check, red stops for a full look — so a reviewer's eye lands exactly where judgment is actually required and nowhere else. The interface itself stays out of the way: a dark, quiet "console" surface with almost no decoration, so the one signal that is allowed to be loud — the confidence band on a field — is never competing with anything else for attention.
The system is calm, precise, and unhurried by design, because the job it supports is not: a reviewer working through dozens of patients per document needs an interface that does not add its own visual noise on top of already noisy handwritten source material. Confirmed rejection: no bright accent palette, no illustrative flourishes, no "friendly SaaS" warmth — this is an operations tool for people doing careful, repetitive verification work, and it should feel like a well-run night shift, not a marketing site.
Key Characteristics:
- One loud signal only — the green/amber/red confidence band — everything else recedes
- Flat by default; depth appears only as a response to state, not as decoration
- Dense, scannable typography tuned for many short fields in a row, not prose
- Every screen answers "what needs me right now" before anything else
Colors
Two appearances — Light and Dark, and only those two — declared once as CSS
variables in web/src/index.css and registered there as Tailwind utilities via
@theme inline. A component names a token (bg-surface, text-muted,
border-line); it never names a hex. If a colour decision is not in that file,
it is not a colour decision this product makes.
@theme inline rather than plain @theme is load-bearing: inline emits each
utility as var(--surface) at the use site instead of snapshotting the value,
which is what lets .dark reassign a token and repaint every utility built on
it. Plain @theme would freeze the light value into the stylesheet.
Surfaces and ink
| token | light | dark | what it is |
|---|---|---|---|
ground |
#F4F3F7 |
#0A0912 |
the page behind everything |
rail |
#FFFFFF |
#100E1A |
the sidebar, and the review screen's own strips |
surface |
#FFFFFF |
#151220 |
cards, panels, tables |
surface-2 |
#F7F6FA |
#1B1727 |
inset things: inputs, chips, wells |
line |
#E2DFE9 |
#2A2438 |
the one border colour |
line-soft |
#ECEAF1 |
#221D31 |
the divider between rows of a list |
ink |
#18151F |
#F3F1F8 |
anything being read |
ink-2 |
#3D3850 |
#C9C4D8 |
text that supports it |
muted |
#66607A |
#8D87A0 |
labels and captions, found rather than read |
The ink ramp is three steps on purpose. Anything reaching for a fourth is usually reaching for the wrong one of the three.
Brand ramp — the only saturated thing on a page
- Accent 1 (
#FF3049) → Accent 2 (#F26B21), identical in both appearances, and the same two hues the sign-in screen uses — so signing in is a continuation rather than a change of product. linear-gradient(115deg, …)on controls,100degon the wide header band.- It appears on exactly six things: the header bar, primary buttons, the active nav row's 3px bar and 12% wash, the super-admin badge, the review progress fill, and a batch id. Nothing else gets the gradient.
Grading — semantic, never decorative
- Clean (
#51A68F): legibility ≥0.95 — clean print, passes straight through. - Verify (
#E39B34): 0.90–0.95 — sampled rather than checked one by one. - Review (
#DE5243): <0.90 — handwritten or hard to read, always reviewed. - Absent (
#6B6580): no legibility signal at all. Grey because nothing was read, so there is no clarity to grade — distinct from "read badly".
These appear on status pills, the 3px grade rule down a field input, legend
swatches, donut segments and quality meters. Never on a button, never on a
heading. The moment green also means "primary action", neither reading
survives. BAND_VAR in web/src/bands.ts is the one map from a band to its
token.
The workbook fills are not interface colours
confidence-green/amber/red (#B6E2B6 / #F4E2B6 / #F4B6B6) and their
borders are still in the token list and still used — but only for the highlight
chips drawn over the scanned page. They mirror what
patient_extractor_v2.py fills spreadsheet cells with, so a reviewer moving
between the sheet and this screen sees one language. Interface tokens would
break that, and these pastels would be invisible on a white scan besides.
Named Rules
The One Loud Colour Rule. The brand ramp is the only saturated thing on a page, and grading is the only colour carrying meaning. A third loud hue would compete with the one signal the product exists to surface.
Grading Never Becomes Chrome. A grading colour on a button or a heading
destroys both readings at once. The only adjacent case is a destructive action
(Remove on the People screen), which uses review as text — not as a fill.
One Border, One Radius. Every card, panel, table container, dropzone and
empty state is 1px solid line at 14px. Hierarchy comes from spacing, not
from stacking shadows: a page of cards at three elevations reads as a pile, and
every screen in this product is a list of peers.
There Is No Third Appearance. An eye mode — the light palette under a
warm sepia filter — was removed. It was a third surface to keep in sync
forever, it tinted the scanned page a reviewer is trying to read accurately,
and both the OS and the monitor already offer the same warmth system-wide.
Typography
Display Font: Sora 600/700 — page titles, panel headings, figures, the app-bar title, buttons, batch ids, group headers in the rail.
Body Font: IBM Plex Sans 400/500/600 — everything read as prose: body copy, nav labels, table cells, captions, form labels, metadata.
Both load from Google Fonts with display=swap, and every rule that names one carries the full system stack (ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Arial, sans-serif) behind it.
This changed. The interface used to run entirely on the system stack — a density argument for 11–13px lists: at label size, Inter beside Segoe UI is a distinction nobody reading a field list notices, and it cost a network dependency on an internal deployment. That argument was sound in isolation and wrong in aggregate. Once the sign-in screen was rebuilt on Sora/IBM Plex, signing in read as leaving one product and arriving at another. Two families across the whole product is the cheaper inconsistency.
Why these two. They are the sign-in screen's pairing, which is the approved mockup's pairing, carried inward rather than re-chosen. Sora has a real 600 and 700 so weights are drawn rather than synthesised, and its figures hold up at 21–24px where this product puts its money. IBM Plex Sans was drawn for interfaces and stays legible at the 10.5–13px the tables and rails live at.
The rule that survives. A blocked CDN must cost voice and nothing else. No layout figure may depend on either font's metrics without a re-measurement noted beside it, and adding a third family needs a reason written down here first.
Numbers. Anything that sits in a column — money, counts, timestamps, confidence figures, percentages — carries tabular-nums (the tnum class). Figures that shift width as they update are the single most common reason a dashboard looks unstable.
Sign-in scale — a documented exception
The sign-in screen shares the two families but not the size or radius scales. It carries the mockup's own geometry so implementation and mockup stay comparable: radius 20px (card) and 10px (fields, button), type clamp(22px, 3.2vw, 26px) headline, 16px brand name, 14.5px lede/fields/button, 12.5px uppercase labels.
This is a deliberate island, not drift. It is the only surface that is neither a list nor a board, it is seen once per session, and it never renders beside a component built from the scales below — so there is nothing for it to be inconsistent with. A value from that list appearing on any other surface is a bug.
Hierarchy
- Figure Lead (700 weight, 26px, 1.15 line-height): the one dominant quantity on a reporting surface — total spend on the ROI board. At most one per screen; a second one means neither is the answer.
- Figure (700 weight, 20px, 1.2 line-height): a supporting quantity that still has to be read before its own label — the settled-field percentage, each side of the by-hand comparison.
- Heading (700 weight, 16px, 1.25 line-height): patient name in the review header, document title, the brand wordmark.
- Control (600 weight, 13px, 1.3 line-height): navigation that has to be found before it is read — the Extraction/Automation tabs, a batch card's name, a primary button on the batch boards.
- Body (400 weight, 12px, 1.4 line-height): field values, document metadata, most UI copy.
- Label (500 weight, 11px, 1.3 line-height): field names, filter pills, band captions, timestamps.
Named Rules
The Flat Hierarchy Problem — partially addressed. The shipped UI used only 11px, 12px, and 16px, roughly 1.45:1 from smallest to heading. A hierarchy that flat asks color and weight to do all the work vision normally gets from size. The Control step (13px/600) was added for the batch-board and tab-bar chrome, which sits above the data-dense lists and needs to be found without competing with a heading. The ramp is now 11/12/13/16/20/26. 16px stays the ceiling inside any data-dense list, reserved for the one heading that anchors the view; 20px and 26px appear only on surfaces that are not lists — today that is the ROI board, which carries both. The sign-in screen no longer counts toward this ramp: it runs on the signin-* steps instead, for the reasons in Typography > "Sign-in scale".
Sign-in screen
The one screen with nothing else on it, so the brand sweep carries the whole
left panel rather than a bar across the top — the only place in the product
where the palette is decorative rather than functional. Below lg that panel
collapses entirely and the form takes the full width: a half-screen of
decoration would push the fields under the fold on a laptop. Type runs
26px headline / 20px title / 13px controls / 12px supporting copy.
Display — removed. There used to be a 44–72px step
(clamp(2.75rem, 4.6vw, 4.5rem)) for a product wordmark set across the waist
of a viewport-tall monogram. Both are gone: the letter spanned the screen with
the card sitting on its spine, so the parts that showed around the card read as
a ring rather than as a letter. Nothing now exceeds the 26px ceiling, and the
sign-in screen's own type is the signin-* steps in the token block above.
The rule divides the lockup. The name sits above the brand rule and
AUTOMATION below it, so the rule that cuts the S is also the line of the
wordmark — one gesture doing both jobs. The whole block is therefore rotated
onto the rule's own −8° axis: set horizontal, the rule climbs ~53px across the
width of the name and clips a letter at one end or the other, and the dodge
(parking the wordmark in the clear space above) reads as a wordmark avoiding a
line. Parallel, the gap stays constant at every viewport width. The tilt is
structural, not stylistic — it is the only reason the geometry holds without
pinning a gap to a text width the font resolves at runtime.
Implementation note: the lockup's container is h-0 at top-1/2, so it shares
the rule's rotation pivot and its own axis is the rule. The two lines are then
offset by fixed distances (0.5rem / 0.85rem) rather than by anything computed
per breakpoint. The offsets are deliberately unequal because cap height is not
box height — uppercase leaves ~8px of empty descender space below the 72px line
and only ~2px above the 20px one, so equal offsets read as unequal air.
It is sized like a headline but weighted like a background: white at 88% stepping to 52% across a gradient raked to match the light behind it, so the card's 26px pure-white headline is still the loudest type on the screen. That the sign-in headline leads is unchanged — the wordmark is the room, not the sentence. A wordmark at full white here reverses that and the screen becomes a poster rather than a sign-in.
Gated at xl, not lg. Between the two the card's left edge lands roughly
where the wordmark ends; below xl the S carries the screen alone and the
card's own header names the product.
The reveal covers the whole hero, not just the letter. The cursor torch lights the monogram and the wordmark together, as one field: the letter goes brand red, the name goes red-into-orange on the same 150° rake as its resting white gradient. It is one mask on one unrotated, full-size layer that carries both marks — which is the only arrangement that works, since a mask on the wordmark's own container would rotate with it and the torch would slide off the cursor. Everything in the hero is therefore rendered twice, once per layer.
Where the name overlaps the letter, a halo separates them. The name is
placed to clear the monogram's painted edge where the screen is wide enough,
but on squarer screens the letter grows until no channel remains between it and
the card, and the name sits on the letter instead. A single tight dark halo
behind the glyphs keeps the two from tangling. It stays tight on purpose: a
wider one reaches the brand rule ~16px below and veils the very gesture the
lockup is built on. The halo is a filter, never a text-shadow — these glyphs
are a background clipped to text, and a text shadow on transparent text paints
straight through the letterform.
Layout
The document list is a vertical stack of full-width rows on a light Paper
background, each row a single click target. Opening a document replaces the
list entirely with a two-pane, full-window split (draggable divider, ratio
persisted in localStorage): the source PDF on the left inside a Midnight Console Deep canvas, the field-by-field review queue on the right on
Midnight Console. Density is high by design — the review pane favors many
short rows over few tall ones, since the job is scanning many fields fast, not
reading long-form content.
Within the review pane, navigation is patient-by-patient rather than a long scroll: one patient's full field record fills the pane at a time, with Prev/Next controls, a jump dropdown, and left/right arrow-key navigation. The PDF pane follows the active patient's page automatically.
Elevation & Depth
Flat by default. Surfaces sit at the same visual plane at rest — no ambient
drop shadows on cards, panels, or list rows. Depth appears only as a direct
response to state: the currently-open patient card gets shadow-lg shadow-black/20 to read as "in focus," and the PDF page itself gets
shadow-xl shadow-black/40 specifically to read as a physical page sitting on
the dark canvas rather than blending into it. Nothing gets a shadow simply for
decoration.
Named Rules
The Flat-at-Rest Rule. Depth is a state signal, not a texture. If a shadow is not communicating "this is the thing that's active/focused/physical right now," it does not belong.
Shapes
Corners are consistently soft but modest: rounded-md (6px) is the default for
inputs, value chips, and buttons; rounded-lg (8px) for cards and the larger
patient/document containers; rounded-full for pill-shaped filter buttons and
status badges. No sharp corners, no aggressively rounded (>8px) surfaces —
the form language stays understated rather than either clinical-sharp or
playful-round.
Components
Every interactive element is restrained and functional: minimal ornament, a single clear affordance, and just enough state feedback to confirm what happened. Nothing asks to be noticed on its own — the confidence bands already carry that job.
Buttons
- Shape:
rounded-md(6px), except pill-shaped filters (rounded-full). - Primary (Confirm/OK/Approve):
accent-confirmemerald background, dark text, used for "Verify," "OK," and "Approve for posting." - Secondary/Neutral:
bg-slate-800withtext-slate-300, used for Undo, Retry, and non-destructive secondary actions. - Hover / Focus: background lightens one step (
hover:bg-slate-700,hover:bg-emerald-400); no transform or shadow added on hover, consistent with the flat-at-rest rule. - Disabled:
opacity-40pluscursor-not-allowed, used specifically to make the Approve gate's "you can't do this yet" state legible.
Filter Pills
- Style: pill (
rounded-full),bg-slate-800/text-slate-300at rest, inverted tobg-slate-200/text-slate-900when active. - State: exactly one pill active at a time (Red / Amber / Missing code / Unscored / All).
Cards / Containers (Patient record)
- Corner Style:
rounded-lg(8px). - Background:
bg-slate-900(open) /bg-slate-900/60(collapsed, in the list-style views). - Shadow Strategy: none at rest;
shadow-lg shadow-black/20only on the currently-open item. - Border: 1px,
border-slate-800at rest,border-slate-700when open or hovered. - Internal Padding:
px-3.5 py-3header,px-2.5 py-2per field row.
Inputs / Fields (correction input)
- Style:
rounded-md,border-slate-700,bg-slate-800,text-slate-100. - Focus: border shifts to
border-slate-500; no glow/ring — consistent with the flat, restrained component philosophy. - Placeholder:
text-slate-500, always "correct value" — never blank, so an empty input still communicates its purpose.
Confidence Band Chip (signature component)
The one component that is allowed to be visually loud. A field's extracted
value renders inside a solid chip colored by its band (confidence-green /
-amber / -red), dark text for contrast, with a 1px border one shade darker
than the fill. On dark surfaces, unscored/blank never uses the literal
white token — see the Literal-White Trap rule under Colors.
Navigation (document-open header)
- Back control (
← All documents, bordered, neutral) plus the current document's name and template, band legend right-aligned on wide viewports. - No persistent sidebar or tab bar — this is a single-task, single-document focus model, not a multi-section app shell.
Do's and Don'ts
Do:
- Do treat the confidence band as the only color allowed to demand attention on a review screen.
- Do render
unscored/blankfields as a dashed muted chip on dark surfaces, never as literal white. - Do use shadow only as a state signal (open/active/physical), never as ambient decoration.
- Do keep every button, pill, and input restrained — one clear affordance, minimal ornament.
- Do animate
transform/opacityfor progress and state changes, notwidth/height— awidthtransition re-triggers layout on every frame, atransform: scaleX()does not.
Don't:
- Don't introduce a second loud accent hue anywhere near the confidence bands — it competes with the one signal the product depends on.
- Don't add ambient drop shadows to cards or panels at rest; depth is earned by state, not applied by default.
- Don't rely on a single font family across every size and weight without also varying size deliberately — see the Flat Hierarchy Problem: the current 11/12/16px scale is undersized for a system this data-dense and should widen (e.g., toward a 11/13/16/20px, ~1.8:1 progression) the next time typography is revisited.