Matter Status Badges — 6 Color Maps → 1
The same four statuses (Active, On Hold, Closed, Archived) are rendered with different colors in every file that touches them. Status dots vary between circles and squares. Priority badges have two incompatible color schemes. We unify everything into one canonical map.
| Source | Active | On Hold | Closed | Archived |
|---|---|---|---|---|
| home.tsx | Active | On Hold | Closed | Archived |
| clients.tsx | Active | On Hold | Closed | Archived |
| matters.tsx | Active | MISSING | Closed | Archived |
| admin.tsx | Active | On Hold | Closed | Archived |
| search.tsx | Active | MISSING | Closed | Archived |
5 different badge palettes, 3 dot shapes, 2 priority schemes. "On Hold" missing in 2 files. Archived sometimes yellow, sometimes gray.
| Status | Badge | Dot | Rule |
|---|---|---|---|
| Active | Active | green-50 / green-700 | |
| On Hold | On Hold | amber-50 / amber-700 | |
| Closed | Closed | neutral-100 / neutral-600 | |
| Archived | Archived | neutral-50 / neutral-400 |
1 status map, 1 dot style, 1 priority scale. Exported from a shared statusColors constant. All 5 files import the same map.
Why this matters
When "Active" is green-50 on the home page but green-100 on the clients page, users lose trust in the interface. The square vs. circle dot inconsistency makes the app look unfinished. Missing "On Hold" in two files means status filtering silently drops data. A single shared map eliminates all three problems.
Table Headers — 13 Patterns → 2
Every table in the app has a slightly different header style — different font sizes, weights, colors, text transforms, letter-spacing, padding, and background colors. Some are uppercase, some are not. Some have tracking, some do not. We reduce to two intentional patterns: Standard and Compact.
| Name | Status | Modified | Author |
|---|
| Name | Status | Modified | Author |
|---|
| Name | Status | Modified | Author |
|---|
| Name | Status | Modified | Author |
|---|
| Name | Status | Modified | Author |
|---|
| Name | Status | Modified | Author |
|---|
6 shown of 13 total. Differences: font-size (text-xs vs 0.68rem), weight (500 vs 600), color (400/500/600), padding (px-1 to px-4, py-1.5 to py-2), uppercase (yes/no), tracking (none/wide/wider), bg (50/50%2f50/none), border (100/200).
| Name | Status | Modified | Author |
|---|---|---|---|
| Contract draft v3 | Active | 2 hours ago | Morten S. |
| Name | Status | Modified | Author |
|---|---|---|---|
| NDA final.pdf | Closed | Yesterday | Aron T. |
2 patterns. Both always uppercase + tracking-wider. Standard has bg-neutral-50. Compact is transparent. That's it.
Why this matters
Table headers set expectations for column data. When one table uses uppercase tracking and another uses sentence case with no tracking, users have to re-learn how to scan data on every page. The two "non-uppercase" variants (admin.tsx route and Layout.tsx) break the established pattern and look like body text that accidentally ended up in a header row. Standardizing to always-uppercase with consistent padding makes tables instantly scannable.
Primary Buttons — 13 Patterns → 3
Primary buttons use the same blue but with 8 different padding/size combos, one missing font-medium,
one with rounded-lg instead of default radius, and a hover direction bug.
We consolidate to three named sizes: sm, md, lg.
8 size combos, 1 missing font-medium, 1 wrong border-radius, 1 reversed hover direction. All using the same blue.
- Always
font-medium— no exceptions - Always
rounded— never rounded-lg - Hover always darkens — never lightens
- No height-based sizing — padding only
3 sizes + 1 destructive variant. Every button in the app maps to exactly one of these. Exported as shared Tailwind @apply classes.
Why this matters
Eight nearly-identical buttons with subtly different padding makes the UI feel handmade in the wrong way. The missing font-medium on one button makes it look faded. The rounded-lg on the error page breaks visual consistency. The reversed hover direction on a destructive button is a genuine UX bug — users expect darker = more committed, lighter = retreating. Three named sizes eliminate all ambiguity.
Input Focus Rings — 6 Styles → 1
Focus indicators are critical for accessibility and perceived quality. Currently, every form context uses a different combination of ring width, ring color, border behavior, and opacity. One context has no focus style at all. We unify to a single, reliable pattern.
6 focus styles: ring-1/ring-2/none, primary-400/500/600, opacity 30%/100%, border transparent/colored/unchanged. One completely broken.
/* Applied globally via @layer base */
1 focus style for everything: ring-1 + border match. Teal-700 matches the brand. Applied in the base layer so it can't be forgotten.
Why this matters
Focus indicators are a WCAG requirement. The broken agent selects have zero focus feedback, which is an accessibility failure. The five other variants create a flickering, inconsistent experience when tabbing through forms — some rings are thick, some thin, some colored, some transparent. A single focus style applied at the base layer means developers never need to think about it, and keyboard users get reliable, predictable feedback everywhere.
File Type Badges — 4 Implementations → 1
File type badges (PDF, DOCX, XLSX, etc.) are shown in four different places with four different implementations —
different sizes, different color mappings, and one context where all types are gray. XLSX is even colored wrong in chat.
We create one shared <FileTypeBadge> component.
4 implementations, 3 different sizes, 1 wrong color (XLSX blue instead of green), 1 context with zero differentiation.
| Type | Background | Text |
|---|---|---|
| red-50 | red-600 | |
| DOCX / DOC | blue-50 | blue-600 |
| XLSX / XLS | green-50 | green-600 |
| PPTX | purple-50 | purple-600 |
| MSG / EML | amber-50 | amber-600 |
| Other | neutral-50 | neutral-500 |
1 component, 2 sizes (standard + compact), 1 color map. Always has background. Never text-only. Never all-gray.
Why this matters
File type badges serve a single purpose: let users quickly identify document types. When the same PDF badge looks different in four places — or when XLSX is colored blue (Word's color) instead of green (Excel's color) — that purpose is defeated. The MatterDetailPage's all-gray badges are the worst case: they take up space without providing any information. A shared component guarantees consistency, and the canonical color map matches the colors users associate with each file type from Microsoft Office and Acrobat.
text-lg font-semibold text-neutral-900 (18px). One size, one weight, one color.
| Function | Output | Source |
|---|---|---|
| utils.formatDate() | Feb 25, 2026 | utils.ts |
| utils.formatRelativeTime() | 2 days ago | utils.ts |
| search formatRelativeDate() | 2d ago | search.tsx |
| TaskCards formatDate() | Feb 25 | TaskCards.tsx |
| toLocaleString('en-GB') | 25/02/2026, 14:32:00 | documents.tsx |
| Variant | Output | Use case |
|---|---|---|
| 'full' | Feb 25, 2026 | Detail views, exports |
| 'short' | Feb 25 | Tables, cards (same year) |
| 'relative' | 2 days ago | Activity feeds, comments |
| 'compact' | 2d ago | Tight UI (search results) |
en-GB, producing DD/MM/YYYY in an otherwise all-American-format app. Fix: One formatDate(date, variant) utility with four intentional variants, all en-US.
<EmptyState> component with consistent padding (py-8), a single text color (neutral-600), optional icon, optional subtext, and optional action link.
rounded-xl shadow-xl overflow-hidden with a 3-part layout (header, body, footer).