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.
The system agrees on these before anything else.
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.
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.
Chrome is slate-and-white. Color is semantic and used only on data — confidence bands, status, score. Decoration stays monochrome.
Percentages are deliberately not shown. Candidates fall into Likely match, Uncertain, or Unlikely match — a shape a reviewer can argue about, not a decimal that pretends to be truth.
Geist Sans for everything UI. Focus is a neutral 2px ring at 30% opacity — never chromatic. Radius is 10px for cards, inputs, buttons; 9999px for pills and dots. No exceptions without a reason.
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.
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.
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. Always paired with a label; never the only indicator.
Neutral rail — chrome, surfaces & type
Slate 50 → 900Primary — near-black
Actions, focus anchorSemantic — color on data only
100 fill · 200 border · 800 textAbandoned directions
Do not reintroduceOne 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 SansTabular numbers
Stats, progress, countsTen-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 familyShadows
Hover-reveal onlyA 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-alignedTwo surface modes
Pages vs. workspacesDashboard, settings, credits, new position. max-w-6xl mx-auto px-6 py-6. Card-based, page-level scrolling. Users pass through quickly.
Candidate review. Full-width master-detail with independent panel scrolling. Users spend time here.
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 pxWrite 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 productVocabulary
The list we actually ship fromThe 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 Likely 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.
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 · destructiveSizes & states
sm · default · lg · icon · disabledWith glyph
Gap is 8px; icons are 16 pxFour 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 positionConfidence bands
Replaces a numeric scoreQualification verdict
Per criterion, per candidateCandidate status
Reviewer's verdictGeneric shadcn variants
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
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
dashboardRoomy — auth card
standaloneShared 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 · explanationDropdown menu
actionsTable
candidate list| Candidate | Band | Evidence | Status |
|---|---|---|---|
| Anika Verbruggen | Likely match | 4 quotes | Reviewed |
| Milan Peeters | Uncertain | 3 quotes | Needs check |
| Sara Benali | Unlikely match | 2 quotes | Queued |
Skeleton
loading stateConfirmations
alert dialogConfirm dialog
destructive · async pendingForm primitives
checkbox · textarea · messageReview workspace
tabs · sheet · popover · togglesNavigation 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.
Sidebar — expanded
Active state is weight, not pillThree 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>.