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
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...
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.
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
Evidence coverage is partial
Two resumes still need manual review before this position can be closed.
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.
Upload failed
The parser could not extract readable text from one document.
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.
2 files could not be uploaded
The rest of your upload was saved — re-upload the 2 files below.
CV Hamza Fadli PhD.pdfOnly PDF and DOCX files are supported.
GHENT PhD CV (ANNET).pdfFile must be 10 MB or smaller.
Dropdown menu
actions
Senior data engineer
147 candidates · last analyzed today
Table
candidate list
Candidate
Band
Evidence
Status
Anika Verbruggen
Suggested match
4 quotes
Reviewed
Milan Peeters
Possible match
3 quotes
Needs check
Sara Benali
Weak match
2 quotes
Queued
Skeleton
loading state
Analyzing uploaded resumes
Stepper
setup wizard · ordered steps · single active
Vacancy
Confirm criteria
3Upload resumes
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.
Uploading file
Extracting text
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
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
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
15A
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
15D
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
65D
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
Pages own their H1. Every routable page renders <PageHeader title="…">. Nothing else duplicates the route name.
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.
Orientation is sidebar + page H1. Two signals, one from the shell and one from the page. No third.
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.
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
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.
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.
Account deletion is handled manually during the pilot. Contact an administrator to remove your account and associated data.
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
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.
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.
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.
aria-current="step" on the current row. The visual cue is paired with the assistive-technology cue, not a substitute.
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
One scanned resume was parsed with lower confidence.
Analysis running
4 of 6 resumes complete.
Export pending
Available when analysis completes.
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 2Partial 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.
3 shown of 147. Sorted by evidence coverage.
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.
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.
22 credits required. 8 credits available. No analysis has started.
One file failed to parse
The rest of the queue is ready. Retry the failed file, skip it, or remove it.
1 failed file
No credits were spent for this file.
jonas-mercier.pdf
Parser timed out at 60 seconds.
Failed
Files removed
Original files were deleted. Evidence, notes, and summaries remain on the record.
OCR fallback used
The scan was readable, but lower-confidence passages should be checked before deciding.
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
Criterion
Evidence found
Partial
Not met
Evidence of curriculum governance(required)
2
3
0
Academic quality assurance(required)
2
2
1
Stakeholder work(nice to have)
1
2
2
5 items · 3 reviewed
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
Criterion
Mira JanssensSuggested match
3/4 required evidence found
Lara VerheydenSuggested match
4/4 required evidence found
Required
Met. Led the annual programme-board review for 12 course units.
Met. Owned the faculty curriculum committee for three academic years.
Partial. Prepared accreditation files and student-outcome summaries.
Met. Led NVAO accreditation submission, 2022 and 2024 cycles.
Nice to have
No supporting quote found.
Partial. Skills mention Dutch and English stakeholder communication.
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.
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.
31Text link
One sober link for prose and policy. Underlined, foreground, neutral focus ring.
The real <TextLink> from @evalgist/design-system. Use it for legal and policy links instead of hand-rolling an anchor className. Pass asChild to wrap next/link; render a plain anchor for external URLs.
Inline (default)
Underlined in running text; underline drops on hover
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
User
Role
Usage
Status
nora@university.edu
Admin
3 shortlists
Active
viktor@institute.org
User
1 shortlist
Active
lena@college.be
User
7 shortlists
Suspended
Without toolbar
plain framed table
User
Role
Usage
Status
nora@university.edu
Admin
3 shortlists
Active
viktor@institute.org
User
1 shortlist
Active
lena@college.be
User
7 shortlists
Suspended
Wide — horizontal scroll
overflows the column; edge fade appears
User
Role
Org
Usage
Cost
Status
Created
Last seen
Plan
Actions
nora@university.edu
Admin
University of Ghent
3 shortlists
€42.10
Active
2026-01-14
2026-06-16
Pilot
View · Edit
viktor@institute.org
User
Royal Institute
1 shortlist
€8.75
Active
2026-03-02
2026-06-15
Pilot
View · Edit
lena@college.be
User
Merelbeke College
7 shortlists
€118.40
Suspended
2025-11-28
2026-05-30
Trial
View · 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.