Internal reference

A system for evidence-first software.

Sober monochrome chrome. Color reserved for data. One family, one focus ring, one radius. Every component below mounts the real primitive from @evalgist/design-system — if the package drifts from the spec, this page breaks.

Evalgist·Design system·v0·Geist Sans · Geist Mono
02Principles

The system agrees on these before anything else.

Principle 01
Evidence, not verdicts

Every AI judgment is anchored to a literal quote or reference from the source. The product surfaces evidence so the human can defend the decision to a candidate, a student, or an auditor.

Principle 02
Privacy is a claim with a receipt

External surfaces name concrete data handling: EU storage, no AI-provider retention, public service-provider lists, and deletion rules for extracted document text. Avoid “privacy by design” unless the page also shows what that means.

Principle 03
Monochrome chrome

Chrome is slate-and-white. Color is semantic and used only on data — confidence bands, status, score. Decoration stays monochrome.

Principle 04
Bands, not scores

Percentages are deliberately not shown. Candidates fall into Suggested match, Possible match, or Weak match — a shape a reviewer can argue about, not a decimal that pretends to be truth.

Principle 05
One family, one focus ring

Geist Sans for everything UI. Focus is a neutral 2px outer ring at 30% opacity — never chromatic, never changes the border. Radius scales by role: 14px for Card, 10px for mid-surfaces (Dialog, Alert), 8px for controls (Button, Input, Badge), 6px for inline items, 9999px for pills. Headlines and titles use text-balance; body copy and descriptions use text-pretty. Never let a single word strand on the last line.

Principle 06
Quiet motion

Short, mechanical, monochrome. 150–200ms transitions on hover. No bounces, no springs, no fade-ins on page load. Accent animation is reserved for state: the footer live dot, the “Processing” chip.

Principle 07
Text is the hard floor

Ambient decoration — the hero grid, background patterns — declares the text region as an exclude zone. Inside that rectangle, nothing animates. Around it, a feather band scatters the boundary so decoration dissolves into the copy instead of butting against a straight edge. The text region is authoritative; ornament is probabilistic outside it. On phones, decoration is cut entirely — the text takes the full width and reads without peripheral motion.

03Color

Slate is the product. Color is the data.

Chrome is fully monochrome. The moment color appears, it means something — a band, a status, a verdict, or the one brand accent, which is brand and never state. Semantic color is always paired with a label; never the only indicator.

Neutral rail — chrome, surfaces & type

Slate 50 → 900
slate-50#f8fafc
Page bg
slate-100#f1f5f9
Muted
slate-200#e2e8f0
Border
slate-300#cbd5e1
Hairline
slate-400#94a3b8
Placeholder
slate-500#64748b
Muted text
slate-600#475569
Body
slate-700#334155
Emphasis
slate-800#1e293b
Deep
slate-900#0f172a
Titles

Primary — near-black

Actions, focus anchor
--eg-primary
oklch(0.205 0 0)

Brand accent — Editorial Plum

--eg-accent · brand, never state
accent#602e63
Links · rules · dots
accent-soft7% on white
Eyebrow pill fill
accent-border28% on white
Pill border

The accent belongs to the brand, not to data. It appears as marketing and brand furniture (eyebrow dots, links, article rules, OG cards, illustration highlights) and as link color in product chrome — never inside the mark, never on band pills or buttons, and the focus ring stays neutral. Utilities: text-brand, bg-brand-soft, border-brand-border.

Match-band aliases

--eg-band-* · semantic contract for confidence
Suggested match
Suggested match
Strong evidence fit · candidate should rise
--eg-band-likely-bg · --eg-band-likely-fg
Possible match
Possible match
Partial evidence fit · reviewer should inspect
--eg-band-uncertain-bg · --eg-band-uncertain-fg
Weak match
Weak match
Weak evidence fit · still label, never color alone
--eg-band-unlikely-bg · --eg-band-unlikely-fg

Band token reference

Full --eg-band-* family
Suggested match
--eg-band-likelydot--eg-band-likely-bgcard bg--eg-band-likely-bordercard border--eg-band-likely-fgcard fg--eg-band-likely-pill-bgpill bg--eg-band-likely-pill-fgpill fg
Possible match
--eg-band-uncertaindot--eg-band-uncertain-bgcard bg--eg-band-uncertain-bordercard border--eg-band-uncertain-fgcard fg--eg-band-uncertain-pill-bgpill bg--eg-band-uncertain-pill-fgpill fg
Weak match
--eg-band-unlikelydot--eg-band-unlikely-bgcard bg--eg-band-unlikely-bordercard border--eg-band-unlikely-fgcard fg--eg-band-unlikely-pill-bgpill bg--eg-band-unlikely-pill-fgpill fg

Status colors

Use raw rails only for non-band states
Sky
Processing · analyzing

Surface states

Soft fills for transient row states
Amber 50
New since last analysis · drift · diff highlight

Abandoned directions

Do not reintroduce
Violet accent · retired
Indigo-600 · retired
04Type

One family. A deliberate scale. Tabular numbers everywhere that counts.

Geist Sans for UI, Geist Mono for tabular and code-ish content. The scale is Tailwind-aligned and used sparingly — three sizes per screen is enough almost always.

The scale

Geist Sans
Hero (clamp)
clamp 56 → 84px
Evalgist
H1
24px
Review results
Lede
17px
A shortlist backed by evidence — per qualification, per candidate.
Body
14px
Per candidate, per qualification. Evidence anchored to the source document.
Muted
14px
10 of 32 reviewed
Mono
13px
bg-primary text-primary-foreground
Caption
12px
CRITERIA · NOTES · GIST

Eyebrow

.eg-eyebrow
Evalgist · Evaluation tools
Compliance

11px, semibold, uppercase, 0.15em tracking. Default color is fg-3; override with --eg-eyebrow-color for the rare dark variant, as shown above.

Tabular numbers

Stats, progress, counts
147
Resumes analyzed
12
Suggested match
2.4 s
Median per CV
05Shape

Ten-pixel corners. Shadows so light they almost aren't there.

Shape is quiet — the point is content, not chrome. Shadows are hover-reveal only; nothing glows, nothing floats at rest.

Radii

--eg-radius family
sm
6px
menu item
md
8px
dropdown
lg
10px
card · input · button
xl
14px
large card
full
9999px
pill · dot · avatar

Shadows

Hover-reveal only
xs
--eg-shadow-xs
outline button
sm
--eg-shadow-sm
card on hover
md
--eg-shadow-md
auth card · dialog
06Space

A 4-pixel grid. A six-pixel gutter. A page that breathes.

Card padding is p-6 (24px). Section stack is space-y-6. Page max is 72rem (max-w-6xl). Everything else falls out of the 4-pixel grid.

Spacing scale

Tailwind-aligned
1
0.25rem
4px
2
0.5rem
8px
3
0.75rem
12px
4
1rem
16px
5
1.25rem
20px
6
1.5rem
24px
8
2rem
32px
12
3rem
48px

Two surface modes

Pages vs. workspaces
Page mode

Dashboard, settings, credits, new shortlist, shortlist setup. max-w-6xl mx-auto px-6 py-6. Card-based, page-level scrolling. Users pass through quickly.

Workspace mode

Candidate review. Full-width master-detail with independent panel scrolling. Users spend time here.

07Icons

Lucide. 1.5px stroke. Sixteen pixels unless there's a reason otherwise.

One icon set, stroke-based, neutral weight. Always paired with a label or used in a known slot. No sparkle, no emoji, no unicode as icon (except → and · as glyphs).

The working set

lucide-react · 16 px
LayoutDashboard
Sidebar
PlusCircle
New position
Coins
Credits
Settings
Settings
FileText
Resume · document
ChevronLeft
Prev candidate
ChevronRight
Next candidate
ChevronsUpDown
Expand all
Download
Export
AlertTriangle
Parse warning
Menu
Mobile nav
08Voice

Write like someone who knows they're right and doesn't need to raise their voice.

Sober, self-assured, lightly dry. Short sentences. Closer to The Economist than to TechCrunch. No superlatives, no startup language, no emoji, no exclamation marks.

Same meaning, two voices

Real strings from the product
Hero
Wrong
Revolutionize hiring with AI-powered candidate screening!
Right
From applications to shortlist. Fast and fair.
Empty state
Wrong
Oops! No positions found. Get started by creating one now! 🚀
Right
No positions yet. Create your first position to start screening resumes.
Status
Wrong
AI analysis is happening — hold tight, we are working some magic for you...
Right
Analyzing · 22 of 147
Button
Wrong
Unleash the Power →
Right
Start for free

Vocabulary

The list we actually ship from
Use
evidencequoted evidenceevaluateshortlistgradeconsistentfairfasterper criterionper candidateper answerreviewdecideverifyprofessionalhuman decision
Avoid
revolutionarygame-changingunleashsuperchargeseamlesslyeffortlessly10xcutting-edgenext-generationdisruptivebias-freeobjective AIfully compliantAI sparklemagical
09Don'ts

The shortest route to getting the system wrong.

A short list of the mistakes we keep catching in review. Each one looks small; together they are what separates 'looks like Evalgist' from 'looks like a dashboard'.

Color on data only

Never use an accent color on chrome — no colored nav, no colored cards, no colored borders. When in doubt, use slate.

Bands, not scores

Don't show 87%. Show Suggested match. A reviewer can argue about a band; a decimal pretends to be truth.

Active state: weight, not pill

The active sidebar item gets font-medium + text-foreground. No pill background, no left bar, no rounded highlight. The absence of decoration is the decoration.

Empty states earn their keep

Every empty state tells the reader what's there and what to do next. Two short sentences. No mascot, no “Oops!”, no exclamation.

10Buttons

Four variants. One size by default. The primary is near-black.

Every button below is the real <Button> from @evalgist/design-system. If a variant gets renamed or removed, this page will stop compiling.

Variants

primary · outline · ghost · destructive

Sizes & states

sm · default · lg · icon · disabled

With glyph

Gap is 8px; icons are 16 px
11Badges

Four jobs. Four palettes. Always paired with a glyph or a label.

Every badge below is the real <Badge> from the package — if the cva config drifts, these chips will look wrong or the build will fail.

Job status

Lifecycle of a position
DraftAnalyzingCompleteError

Confidence bands

Replaces a numeric score
Suggested matchPossible matchWeak match

Qualification verdict

Per criterion, per candidate
Evidence foundPartial evidenceNo quote foundNot checked

Candidate status

Reviewer's verdict
ShortlistedRejectedUndecided

Origin

Where a criterion came from
From vacancyManualAI suggestion

Generic shadcn variants

DefaultSecondaryDestructiveOutline
12Inputs

Slate border, white fill, near-black text, neutral focus ring.

Real <Input>, <Label>, and <Select> from the package. The focus ring is the canonical neutral 2px at 30% — never chromatic.

Form fields

Select

13Cards

White fill. Slate-200 border. Ten-pixel corners. Lifts only on hover.

All real <Card*> primitives from the package. Padding is p-6 (roomy) or p-4 (dense).

Dense — job card

dashboard
Senior data engineer · Ghent
Opened 2 days ago · 147 resumes analyzed
Complete
12 Suggested match48 Possible match87 Weak match
Reviewed: 10 of 147

Roomy — auth card

standalone
Welcome back
Sign in to continue reviewing.
14Primitives

Shared primitives for dense product surfaces.

Batch A and B primitives cover status, menus, tables, confirmations, forms, and notifications. Product-specific rows, filters, and panels stay in the app.

Alert and tooltip

status · explanation
Credits added
Your balance is now 102 credits. Success and warning alerts announce politely (role="status"); only errors interrupt with role="alert".
Credits added
90 credits added. You now have 102 credits. Confirmation keeps the chrome neutral and reserves emerald for the icon — a quieter status than success, still role="status".
Running low
You have 2 credits left. Warning reserves amber; red is for the empty state.

Alert — error with file manifest

destructive · composed

The destructive variant carries a faint tinted surface and edge so an error reads as a distinct surface, not just red text; the body and the per-item manifest stay neutral for legibility. Compose a two-column manifest inside AlertDescription (filenames in an aligned column, reasons in the second) and put the dismiss in AlertAction.

Dropdown menu

actions
Senior data engineer
147 candidates · last analyzed today

Table

candidate list
CandidateBandEvidenceStatus
Anika VerbruggenSuggested match4 quotesReviewed
Milan PeetersPossible match3 quotesNeeds check
Sara BenaliWeak match2 quotesQueued

Skeleton

loading state
Analyzing uploaded resumes

Stepper

setup wizard · ordered steps · single active
  1. Vacancy
  2. Confirm criteria
  3. 3Upload resumes
  4. 4Review evidence

Processing panel

upload · parsing · import

Extracting vacancy text

Keep this page open while Shortlist reads the file and prepares the criteria review.

4s
Extracting text and identifying criteria.
  1. Uploading file
  2. Extracting text
  3. Preparing review

Job_Posting.docx

Reading file and preparing criteria review.

45 KB

Confirmations

alert dialog
Archive position
Confirmation is explicit when the action changes evidence state.

Confirm dialog

destructive · async pending
Neutral confirmation
Default variant. The confirm button uses the primary action style.
Destructive confirmation
Async confirm. The dialog shows a pending state and stays open if the action throws.

Recovery dialog

blocked action · kept work
Active shortlist limit
Use when an action is blocked but the user's current work can still be recovered.

Form primitives

checkbox · textarea · message

Keep notes factual and anchored to evidence.

The human decision remains separate from the AI band.

Review workspace

tabs · sheet · popover · toggles
Four quotes support the current band. One quote requires reviewer confirmation.
Resume
Document excerpt and highlighted quote.
Evidence
Band rationale and reviewer controls.
15Chrome

Navigation that disappears once you know where you are.

Sidebar and topbar are Shortlist-specific, so they don't live in the shared package. They're shown here as a reference for how the tokens and primitives compose. See section 16 for the page-level contract and the PageHeader primitive that pairs with this chrome.

Sidebar — expanded

Active state is weight, not pill
Dashboard
Open positions
Dashboard content lives in apps/shortlist.

Sidebar parts

App-local, in apps/shortlist/components/layout
EvalgistShortlist
sidebar-header.tsx
Dashboard
sidebar-nav-link.tsx
Admin
admin-nav-group.tsx — operator console, collapsed by default; opens on /admin
KKarel Van AchteAdmin
sidebar-identity.tsx — account-menu trigger (Account, Sign out); Admin badge for operators
16App shell

The page owns its title. The topbar carries context.

Pages render their H1 via PageHeader. The topbar carries utility plus page-provided route context: a single immediate-parent back-link on nested detail routes, or a compact breadcrumb only when a multi-route wizard needs to expose its intermediate steps. Ordinary routes and sidebar-backed creation routes leave the left slot empty. Reading the URL to reconstruct the current route name in the topbar remains forbidden.

PageHeader — anatomy

Eyebrow, title, description, actions

Billing

Credits

Track evaluation credits, monthly usage, and screening outcomes.

BalancePill — three states

Slate ramp, no traffic-light hues
42ok
3low
0empty

States differ in contrast, not hue. Brand contract is sober — no mint, no amber, no red.

Minimum form

Title is the only required prop

Open positions

Custom title and eyebrow slots

title, eyebrow, and description accept ReactNode
Draft

Senior Operations Manager

Pass a node for editable titles, badge rows, or rich descriptions. Strings keep the default chrome.

Canvas and cards

Three tiers, card brighter than canvas
Canvasbg-slate-100 / --eg-muted

Page background. Slightly grey, never white.

Cardbg-card / --eg-card

Content surface. One step lighter than canvas.

Chromebg-card / --eg-card

Sidebar & topbar. Same tier as cards, separated by a single border.

With shell, no duplicate title

Sidebar highlights, page header names
15

Billing

Credits

Track evaluation credits, monthly usage, and screening outcomes.

Page content lives in the consuming app.

Sidebar-backed creation route

Active sidebar item plus H1 are enough
15

New shortlist

The sidebar already marks New shortlist as active. The topbar left slot stays empty so the shell does not add a redundant Dashboard back-link.

Nested route topbar

Collapsed sidebar plus one immediate parent link; never sibling tabs
Dashboard
65

Review

Doctoral Research Assistant in Transcriptomics and Biotechnology

The page H1 names the shortlist. Because the shortlist home is a redirect-only route, the back-link lands on the dashboard and uses that landing-page label.

Composition rules

Codified in packages/design-system/APP_SHELL.md
  1. Pages own their H1. Every routable page renders <PageHeader title="…">. Nothing else duplicates the route name.
  2. The topbar is utility plus route context. Identity, credit balance, global actions, plus a page-provided parent back-link (or a breadcrumb on multi-route wizards). The topbar never derives the active route name from the URL.
  3. Orientation is sidebar + page H1. Two signals, one from the shell and one from the page. No third.
  4. Parent context belongs in the topbar. Ordinary depth-0/1 routes leave the left slot empty. Sidebar-backed creation routes also leave it empty. Depth-2+ routes render one immediate parent back-link, labeled by the parent entity. When that parent is a redirect-only route, the link skips to the nearest landable index and uses the landing-page label. Multi-route wizards may render a breadcrumb of their own intermediate steps. Sibling views never appear there. On small screens the back-link replaces the product lockup in the left slot — route context wins, and the brand stays in the menu drawer.
17Confidence

Three bands. Four qualification verdicts. One progress bar that tells the story.

Confidence is a band, not a percentage — shown in English, backed by evidence. Progress is the <Progress> primitive; the band is a <Badge>.

Match bands

Per candidate · always with a label
Anna De Smet
Suggested match
5/6 met
Pieter Vermeulen
Possible match
3/6 met
Laila El-Khatib
Suggested match
5/6 met
Yves Maes
Weak match
1/6 met

Qualification verdicts

Per criterion
5+ years SQLData warehousingCloud certificationBelgian driver's license
18EmptyState

The inline empty surface. Dashed border, neutral icon plate, one action at most.

<EmptyState> is the section-level empty primitive — drop it inside a section that has no rows yet. The action slot accepts any React node so the design system stays Next-agnostic; consumers compose their own link primitive.

No action — purely informational

admin · settings unavailable

Settings unavailable

The admin settings API did not return model settings.

With an action slot

dashboard · credits page

No screening results yet

Run your first analysis to see what your credits produce.

Left aligned

dashboard · operational first run

No shortlists yet

Create the first shortlist or inspect the built-in sample before uploading any files.

Operational variants

admin · logs and jobs

No positions yet

Positions will appear here after users create them.

No recent errors

Operational logs appear here when the app records warnings or errors.

19Stat cards

Label, number, optional hint. Chrome stays monochrome; color rides the data.

A page-header strip used on dashboards. The icon is optional and quiet; the colored bubble is gone. Destructive tone applies to the value when a number is genuinely bad — never as decoration.

Minimal

label + value

Shortlists

10

Resumes screened

323

Credits

14

With hint

quiet line below the number

Credits

4

Buy more credits

Resumes screened

323

Across 12 shortlists

Linkable

adds a thin trailing arrow that brightens on hover

Credits

14

Destructive tone

value turns destructive; hint follows. Used when the number means trouble — zero credits, failed run, etc.

Credits

0

Buy more credits

With icon

optional, thin, slate-400. Use sparingly — most stats don't need one.

Credits

14

20Trust strip

Small-print row of commitments, sitting under a CTA.

Used on commerce and billing surfaces to reassure right where the decision happens. Middle-dot separators, muted text, screen-readers skip the separator. Three to five items reads best — past that, split or trim.

Default

default separator is the middle dot (·)

Credits valid 12 months Non-refundable No subscription Stripe receipt

With a geo-aware item

render local payment-method names conditionally

Credits valid 12 months Non-refundable Card or Bancontact accepted No subscription Stripe receipt

Custom separator

bullet instead of middle dot

GDPR-compliant EU data residency ISO 27001

21Auto-cycle

Rotating demo with explicit play, pause, and progress.

`useAutoCycle()` drives the active index, owns prefers-reduced-motion, and pauses on hover or focus. `<AutoCycleProgressBar>` and `<AutoCyclePauseToggle>` give consumers a ready-to-place control. The bar pauses in place — hover does not reset progress.

Demo card

Click a row to jump · hover to pause · 3.5 s cycle per candidate
3 candidates · sorted by match
Dr. S. ChenSuggested match

All three qualifications met. 12 peer-reviewed papers on file.

Composition contract

What every consumer wires up
  • Wrap the visual container with {...cycle.containerProps} — that wires pointer and focus pause.
  • Render <AutoCyclePauseToggle cycle={cycle} /> next to the content title so the affordance is discoverable.
  • Render <AutoCycleProgressBar cycle={cycle} /> along the bottom of the same container — the bar drives the cycle.
  • Map your items with cycle.active and cycle.jumpTo(i) for the click-to-pin affordance. Set aria-current on the active item.
  • Under prefers-reduced-motion: reduce, the bar and toggle render nothing and the cycle does not advance. Static state shows item 0.
22FAQ

Three-to-five short Q&As that handle the predictable objections.

Native <details> so it works without JavaScript, collapses by default, opens to one item at a time without state management. Use on commerce, pricing, and onboarding surfaces — anywhere a small set of repeat questions blocks a decision.

In a Card

standard wrapping for a billing or pricing page
Frequently asked
Do credits expire?
Yes. Each credit is valid for 12 months from the date it is granted. Unused credits expire after that.
Can I get a refund?
Credits are non-refundable. Each credit is valid for 12 months from the date it is granted; unused credits can be spent any time within that window.
Will I be charged again automatically?
No. Each purchase is one-time. There is no subscription.

Standalone

without the Card wrapper, e.g. inline on a docs page
What payment methods are accepted?
Stripe Checkout surfaces local methods per buyer country.
Where are my receipts?
Stripe issues a receipt by email on every purchase.
23Danger zone

Destructive actions get a card, a typed guard, and one red accent.

One surface for every irreversible action across products. The industry consensus — Mintlify, Clerk, GitBook, Hume, 1Password, Stripe — is no leading icon: the destructive button is the sole accent, which also matches our monochrome-chrome rule that colour is reserved for the action. The typed-confirmation guard is the safety feature; bypass it and the user can delete data with one click.

Typed-confirmation guard

default for irreversible actions
Delete account
Permanently removes your account, positions, resumes, and analysis results.

No typed guard

for lower-stakes confirms
Clear search history
Removes the cached search queries on this device.

Disabled with notice

action gated by policy
Delete account
Permanently removes your account, positions, resumes, and analysis results.

Optional leading icon

opt-in; a muted warning glyph, never a chip
Delete workspace
Permanently removes the workspace and everything in it.
24Sticky action bar

One pill, one decision.

Surfaces dirty-state in an edit flow and offers the resolution actions inline. The status text is the subject — the actions are the verbs. Pulse dot communicates 'something is pending' without urgency.

Unsaved-changes pattern

status + discard + primary

Unsaved changes

With undo

transient deletion within a save window

Criterion deleted

Tones

warning (default), info, danger, success

Pending review

Analysis running

Quota exceeded

Saved

Interactive

click Save to confirm the action handler fires

Unsaved changes

When to use

rule

Use in edit flows where the user can leave the page mid-change. The bar is sticky-bottom by default; pass className="static" to demo it inline (as on this page). One primary action only — never two. Avoid for global app toasts; the toast primitive is the right tool for transient, non-blocking confirmations.

24Step states

Four states, one cue at a time.

The icon-tile-plus-row pattern used by setup strips and task checklists. State lives in the icon tile and, when current, in a single hairline on the row. The badge says the same thing in words. Never two cues for the same state — pick one.

The strip

One row per state

Role uploaded

Done

Sales Manager parsed

Add role

Current

Upload or paste the role

Evaluation criteria

Blocked

Waiting for role details

Review candidates

Up next

Once screening completes

Each state, isolated

What the tile carries vs. what the row carries

Role uploaded

Done

Sales Manager parsed

state
complete
tile
bg-emerald-50 text-emerald-700
row
badge
variant="shortlist-done"

Add role

Current

Upload or paste the role

state
current
tile
bg-muted text-foreground
row
ring-1 ring-inset ring-foreground/15
badge
variant="secondary"

Evaluation criteria

Blocked

Waiting for role details

state
blocked
tile
bg-muted text-muted-foreground
row
badge
variant="outline"

Review candidates

Up next

Once screening completes

state
not-started
tile
bg-muted text-muted-foreground
row
badge
variant="outline"

Rules

Why this looks the way it does
  1. One cue per state. A ring and a badge for the same row is two voices saying the same word. The badge stays; the row hairline replaces the louder cues we tried first.
  2. Tile fill stays monochrome except for complete. Emerald 50/700 is reserved for finished work — the only state that earns a semantic colour. Current is signalled by the row, not by a tinted tile.
  3. Blocked and not-started share the tile. The badge does the differentiating. Two near-identical tile treatments with different badges is correct; two ways of saying "this is not the active step" is not.
  4. aria-current="step" on the current row. The visual cue is paired with the assistive-technology cue, not a substitute.
  5. Not yet a primitive. The pattern lives inline in apps/shortlist/components/jobs/setup-progress-strip.tsx. When a second surface needs the same icon-tile-plus-state vocabulary, extract <StatusIconTile> into @evalgist/design-system/ui and refer back to this section.
26Workflow primitives

Inspect, recover, continue.

Reusable patterns for operational product states: inspection sheets, compact progress histories, visible action menus, and source-backed evidence blocks. These are deliberately quiet; their job is to preserve context and make the next action unambiguous.

Flow rail mini

static step state; compact phone summary; routing stays with the app

Step 2 of 5

Criteria

Step 2 of 5

Criteria

activeKeysteps[].keysteps[].labelsteps[].iconlinkForLinkComponent

Band badge

canonical match-band labels
Suggested matchPossible matchWeak match
band=strongband=reviewband=low

File row

queue row for upload, parse, and recovery states
  • mira-janssens.pdf

    184 KB · 4 pages

    Parsed
  • thomas-declercq.pdf

    226 KB · scanned PDF

    OCR fallback used. Review evidence quotes before deciding.

    Warning
  • sophie-maes.pdf

    198 KB · uploading

    Uploading
  • jonas-mercier.pdf

    143 KB

    Failed
  • lien-claes.pdf

    102 KB

    Queued
status=readystatus=warningstatus=uploadingstatus=errorstatus=queued

Decision group

three outcomes; keyboard listener belongs upstream
valueonChangedisabledshowShortcuts

Criteria breakdown bar

counts, not false-precision scores
3 met, 1 partial, 0 not met1 met, 2 partial, 2 not met0 met, 0 partial, 0 not met
metpartialnotMet

Suggestions block

mobile-safe amber review surface for generated criteria

Suggested additions

Generated from the role context. Add only the criteria you want to screen against.

  • Doctoral supervision experience

  • Programme-board governance

defaultAction=optionaldefaultAction=requiredonAction

Detail sheet

right-side inspection, not a decision dialog
openonOpenChangefinalFocusRefwidth=wide

Status timeline

one state column, one action column
  1. Vacancy uploaded

    associate-professor-learning-sciences.pdf

  2. OCR fallback used

    One scanned resume was parsed with lower confidence.

  3. Analysis running

    4 of 6 resumes complete.

  4. Export pending

    Available when analysis completes.

  5. Resume parse failed

    jonas-mercier.pdf

    Parser timed out at 60s. 1 credit refunded.

Weight chip

required, nice to have, excluded
RequiredNice to haveExcluded·editable — click to change

Cost breakdown

empty, covered, shortfall, and line-item basis

Add resumes to calculate the credit cost.

Available
15 credits

Credits required

8 credits

Covered by balance

Available
50 credits
Remaining
42 credits

Cost basis

6 standard resumes6 x 1 credit
6 credits
1 scanned (OCR)1 x 2 credits each
2 credits

Refunded if a file fails to parse.

Credits required

22 credits

Need 14 more credits

Available
8 credits
Shortfall
14 credits

Cost basis

18 standard resumes18 x 1 credit
18 credits
2 scanned (OCR)2 x 2 credits each
4 credits

Top up before continuing.

Evidence card

verdict + criterion + quoted source
Evidence found

Evidence of curriculum governance

Led the annual programme-board review for 12 course units.
Source:Resume, page 2
Partial evidence

Experience with academic quality assurance

Prepared accreditation files and student-outcome summaries.
Source:Resume, page 2 (OCR)

Action menu button

visible at rest; destructive separated

Evidence of curriculum governance

Section 2.1, role description

Evidence quote

inline quotation under a claim; left rule + curly quotes, no box

Mira Janssens

Evidence of curriculum governance

Led the annual programme-board review for 12 course units.

Resume, page 2

Source excerpt

verbatim passage; bordered source block opens the document when available
Led the annual programme-board review for 12 course units.
Source:From resume
Operations director at Northgate Foundation since 2014 — 11 years leading the operating team across finance, HR, IT, and facilities.
Liaison for Dutch-speaking student-affairs office. OCR confidence is lower on this page.
Source:Resume, page 2 (OCR)
27Work surfaces

Page chrome recedes. The evidence surface stays white.

These primitives extract the reusable layout spine from the Shortlist sample: a muted route canvas, constrained work area, object identity header, context strip, and composable toolbar. Apps own routing, state, labels, and data.

Route shell

PageHeader, context, optional work surface

Evalgist Shortlist

Senior lecturer in data systems

Review candidate evidence against the committee criteria.

Senior lecturer in data systems

CompleteVacancy criteria

147 candidates analyzed. 18 require committee review before export.

Anika Verbruggen
6 quotes attached to criteria
Suggested match
Reviewed
Milan Peeters
4 quotes attached to criteria
Possible match
Needs check
Sara Benali
2 quotes attached to criteria
Weak match
Queued

Bare work surface

Use when the route already owns the page frame

Evidence needing review

Possible match

A compact object header can sit inside any app-owned route.

12 quotes need human confirmation.
28Workflow patterns

Bounded work surfaces for document flows.

Composite primitives extracted from the Shortlist sample: a muted application canvas, white work surfaces, thin borders, restrained shadows, and dense operational states that preserve the user's work.

Setup rail

complete, current, upcoming, and blocked states

Upload files

The horizontal rail scrolls on narrow screens. The vertical rail is for bounded setup panes where the steps stay visible while the work surface changes.

state=completestate=currentstate=upcomingstate=blockedorientation=horizontalorientation=vertical

Upload queue

dropzone wrapper plus queued FileRow rows

Drop PDFs or DOCX files

Parsing starts when files are added. Analysis starts only after review.

Up to 100 files. 10 MB per file.

5 files, 3 ready, 1 parsing
Original files are kept for 14 days, then deleted.
  • mira-janssens.pdf

    184 KB, 4 pages

    Parsed
  • thomas-declercq.pdf

    226 KB, scanned PDF

    OCR fallback used. Evidence quotes should be checked.

    Warning
  • sophie-maes.pdf

    198 KB, uploading

    Uploading
  • lara-verheyden.docx

    94 KB

    Queued

Blocked and failed states

work is preserved; next action is explicit

Analysis is blocked

The files are parsed and remain queued. Add credits or remove files before starting analysis.

Files removed

Original files were deleted. Evidence, notes, and summaries remain on the record.

Cost preview

compact side panel for usage and balance
29Evidence review layouts

Keep criteria, evidence, and the human decision together.

These patterns come from the Shortlist sample, but the contracts are product-neutral: a queue of items, a dossier, source-backed criteria, and side-by-side evidence when comparison matters.

Split review workspace

overview, toolbar, queue, dossier — queue fills the column height
Criteria overview

Evidence of curriculum governance(required)

Evidence found
2
Partial
3
Not met
0

Academic quality assurance(required)

Evidence found
2
Partial
2
Not met
1

Stakeholder work(nice to have)

Evidence found
1
Partial
2
Not met
2
Suggested match1 / 5

Mira Janssens

Decision: Shortlisted

Gist

Programme director with evidence of curriculum governance and committee leadership. Stakeholder-language evidence needs human confirmation.

Evidence

MetEvidence of curriculum governance
Led the annual programme-board review for 12 course units.
Source:Resume, page 2

Evidence comparison table

desktop table, mobile accordions

Required

Mira JanssensSuggested match
Met. Led the annual programme-board review for 12 course units.
Lara VerheydenSuggested match
Met. Owned the faculty curriculum committee for three academic years.

Nice to have

Object header before review controls

identity stays visible

Associate professor, learning sciences

5 of 5 analyzed

Faculty of Psychology and Educational Sciences

30Motion

Short, mechanical, monochrome. 150–200ms on hover. Nothing bounces.

Motion is the loudest signal a page can send. Quiet motion keeps the focus on the content. Every consumer transition lives within the durations below; ambient and rotating UI uses the named keyframes.

Transition durations

Hover, panels, content swaps
hover · color
150ms · ease-out
buttons, links, rows
hover · shadow
150ms · ease-out
cards, stat-cards on hover-reveal
panel · open / close
200ms · ease-out
dialog, sheet
cross-fade · content swap
300ms · ease-out
auto-cycle detail panel (`animate-in fade-in-0 duration-300`)

Named keyframes

@keyframes shipped from tokens.css
eg-cycle-progress
scaleX(0)scaleX(1)
AutoCycleProgressBar · transform-origin: left · driven by the consumer's `animationDuration`
3500ms · linear · forwards--eg-cycle-progress

Reduced motion

The contract on `prefers-reduced-motion: reduce`
  • AnimatedGridPattern renders nothing below sm (640px) and respects `prefers-reduced-motion: reduce`.
  • AutoCycleProgressBar and AutoCyclePauseToggle render nothing under reduced motion; the cycle does not advance.
  • No fade-in on page load. Use `animate-in fade-in-0` only on content swaps the user triggered.
  • No bounces, no springs, no chromatic ring transitions.
32Surfaces

Two surfaces: the white card, and the grey canvas. Controls adapt to the one they sit on.

Interactive controls hover to --accent. On a white card that slate-100 hover reads fine. On the grey canvas (the app shell paints bg-muted), the same hover equals the canvas and the control disappears. Mark a region data-surface="canvas" and the contract remaps hover one step darker; any card nested inside resets it automatically. No per-call-site opt-in.

On the card surface

white · hover → slate-100

On the canvas surface

bg-muted · hover → slate-200 · controls rest on white and lift

A card on the canvas resets

nested controls behave as on-card again

Hover the buttons above to see it: on the canvas the hover fill is distinct from the grey; inside the card it returns to the standard slate-100. The Select and Input rest on white in both, so they lift off the canvas instead of sinking into it.

33Data table

Filters belong to the table, not floating above it.

<DataTable> wraps the real <Table> in its own card surface with an optional toolbar — a non-scrolling strip on the same surface as the rows. The body scrolls horizontally for wide tables while the toolbar stays put. Distinct from <ActionToolbar>, which is a standalone bulk-action bar that sits above content.

With toolbar

search · filter · count, attached to the table
3 shown
UserRoleUsageStatus
nora@university.eduAdmin3 shortlistsActive
viktor@institute.orgUser1 shortlistActive
lena@college.beUser7 shortlistsSuspended

Without toolbar

plain framed table
UserRoleUsageStatus
nora@university.eduAdmin3 shortlistsActive
viktor@institute.orgUser1 shortlistActive
lena@college.beUser7 shortlistsSuspended

Wide — horizontal scroll

overflows the column; edge fade appears
UserRoleOrgUsageCostStatusCreatedLast seenPlanActions
nora@university.eduAdminUniversity of Ghent3 shortlists€42.10Active2026-01-142026-06-16PilotView · Edit
viktor@institute.orgUserRoyal Institute1 shortlist€8.75Active2026-03-022026-06-15PilotView · Edit
lena@college.beUserMerelbeke College7 shortlists€118.40Suspended2025-11-282026-05-30TrialView · Edit
34Refresh button

Reload is utility chrome, not a call to action.

<RefreshButton> is an icon-only ghost control: no label competing with the page title. The name lives in an aria-label and a tooltip; the glyph spins and the control disables while a refresh is in flight. It belongs in the page header's actions slot. Requires a TooltipProvider ancestor — the app shell mounts one.

In a page header

actions slot · on the canvas surface

Admin

Users

Find users, inspect usage, grant credits, and manage support status.

States

rest · refreshing (spins, disabled)