Gustav DMS — Style Audit: Before & After

How each element looks today vs the proposed standard. Before panels use Gustav's actual current styling (system font, sky-blue primary). After panels show the proposed fix.

1

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.

Before
Status badge colors by source file
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
Status dots — 3 shapes
browse.tsx w-2 h-2 rounded-full (circle)
matters.tsx w-2 h-2 NO rounded-full (square!)
home.tsx sidebar w-1.5 h-1.5 smaller, square
Priority badges — 2 incompatible schemes
Main app High Medium
AdminPage High Medium

5 different badge palettes, 3 dot shapes, 2 priority schemes. "On Hold" missing in 2 files. Archived sometimes yellow, sometimes gray.

After
One canonical status map — all files
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
Unified priority badges
High Medium Low
All dots = w-2 h-2 rounded-full
One size, always round

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.

2

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.

Before
6 of 13 real table header patterns
AdminPage main
Name Status Modified Author
ClientsListPage
Name Status Modified Author
MatterDetailPage
Name Status Modified Author
RecentDocumentsPage
Name Status Modified Author
admin.tsx route — NO uppercase, NO tracking
Name Status Modified Author
Layout.tsx — NO uppercase, NO tracking
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).

After
Standard — full-width data tables
Name Status Modified Author
Contract draft v3 Active 2 hours ago Morten S.
text-xs · font-medium · text-neutral-500 · uppercase · tracking-wider · bg-neutral-50 · px-3 py-2
Compact — inline / sidebar tables
Name Status Modified Author
NDA final.pdf Closed Yesterday Aron T.
text-xs · font-medium · text-neutral-400 · uppercase · tracking-wider · no bg · px-2 py-1.5

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.

3

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.

Before
8 real button patterns at actual size
AdminPage tab — px-2 py-1 text-xs
Task comment — px-2.5 py-1 text-xs
Matter inline — px-3 py-1 text-xs MISSING font-medium
Modal footer — px-3 py-1.5 text-sm
Agent modal — px-4 py-1.5 text-xs
Admin modal — px-4 py-2 text-sm
Toolbar — h-6 px-2 (height-based, not padding)
Error page — px-4 py-2 text-sm rounded-lg (different radius!)
Hover direction bug
Darkens on hover
Lightens on hover!
bg-red-700 hover:bg-red-800 (correct) vs bg-red-700 hover:bg-red-600 (wrong — goes lighter)

8 size combos, 1 missing font-medium, 1 wrong border-radius, 1 reversed hover direction. All using the same blue.

After
3 named sizes — sm, md, lg
btn-sm
px-2.5 py-1 text-xs font-medium rounded
btn-md
px-3 py-1.5 text-sm font-medium rounded
btn-lg
px-4 py-2 text-sm font-medium rounded
Destructive variant
Always darkens on hover: bg-red-600 → hover:bg-red-700
Rules
  • 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.

4

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.

Before
6 focus styles (shown in "focused" state)
Modal inputs — ring-1 ring-primary-500
Admin modals — ring-2 ring-primary-500, border goes transparent
Task search — ring-2 ring-primary-500/30, border-primary-400
Agent modals — ring-1 ring-primary-400, border-primary-400
Admin forms — border-primary-600 only, NO ring
Agent selects — NO focus style (broken!)
Click does nothing — zero visual feedback

6 focus styles: ring-1/ring-2/none, primary-400/500/600, opacity 30%/100%, border transparent/colored/unchanged. One completely broken.

After
One focus style — all inputs, selects, textareas
The rule
focus:border-teal-700 focus:ring-1 focus:ring-teal-700
/* 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.

5

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.

Before
FileTypeBadge component
PDF DOCX XLSX
px-1.5 py-0.5 text-xs font-medium
home.tsx compact
PDF DOCX XLSX
text-[9px] px-1 py-px font-medium uppercase — TINY
ChatComponents
PDF DOCX XLSX
Text-only, no background. XLSX = text-blue-600 (should be green!)
MatterDetailPage portal
PDF DOCX XLSX
All gray — no type differentiation at all
Same file type, 4 appearances
PDF: PDF PDF PDF PDF

4 implementations, 3 different sizes, 1 wrong color (XLSX blue instead of green), 1 context with zero differentiation.

After
One <FileTypeBadge> component — two sizes
Standard
PDF DOCX XLSX PPTX MSG
px-1.5 py-0.5 text-xs font-medium rounded
Compact (sidebar, dense lists)
PDF DOCX XLSX PPTX MSG
px-1 py-px text-[10px] font-medium rounded
Color map (canonical)
Type Background Text
PDFred-50red-600
DOCX / DOCblue-50blue-600
XLSX / XLSgreen-50green-600
PPTXpurple-50purple-600
MSG / EMLamber-50amber-600
Otherneutral-50neutral-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.

6
Form Labels
4 different label combos across the codebase → 2 clean patterns
BEFORE — 4 label styles, each slightly different
Modal forms
text-sm font-medium text-[#404040] mb-1
Admin inline
text-xs text-[#737370] mb-1 (no font-medium!)
TaskCards
text-xs font-medium text-[#a3a3a0] mb-0.5
DevPage
text-xs font-medium text-[#525250] mb-1
AFTER — 2 patterns: Standard and Compact
Standard (modals, full forms)
text-sm font-medium text-neutral-700 mb-1.5
Compact (inline fields, cards)
text-xs font-medium text-neutral-600 mb-1
Problem: Four different label styles with different sizes (text-sm vs text-xs), weights (some missing font-medium), colors (#404040 / #737370 / #a3a3a0 / #525250), and spacing (mb-0.5 vs mb-1). Fix: Two intentional patterns — Standard for full forms (text-sm, neutral-700) and Compact for inline/card fields (text-xs, neutral-600). Both use font-medium consistently.
7
Page Titles
4 different font sizes for the same semantic element → 1 consistent size
BEFORE — 4 sizes for page titles (12px to 18px)
Home 18px · text-lg font-semibold
Clients 16px · text-base font-semibold
Recent Documents 14px · text-sm font-semibold
Andersen Acquisition 12px · text-xs font-semibold
A 12px page title is visually indistinguishable from body text
AFTER — All page titles at 18px
Home 18px
Clients 18px
Recent Documents 18px
Andersen Acquisition 18px
Uniform text-lg font-semibold text-neutral-900 for every page title
Problem: Page titles range from 18px down to an absurd 12px. A 12px "page title" has zero visual hierarchy — it is smaller than most body text. Fix: Every page title uses text-lg font-semibold text-neutral-900 (18px). One size, one weight, one color.
8
Date Formatting
4+ formatting functions, inconsistent locales → 1 utility with intentional variants
BEFORE — 5 outputs from the same timestamp
Input: 2026-02-25T14:32:00Z
FunctionOutputSource
utils.formatDate()Feb 25, 2026utils.ts
utils.formatRelativeTime()2 days agoutils.ts
search formatRelativeDate()2d agosearch.tsx
TaskCards formatDate()Feb 25TaskCards.tsx
toLocaleString('en-GB')25/02/2026, 14:32:00documents.tsx
en-GB locale — DD/MM/YYYY with 24h time. Every other date in the app is en-US format. This is an accidental copy-paste from a Stack Overflow answer.
AFTER — 1 utility, 4 intentional variants, all en-US
formatDate(date, variant) — single source of truth
VariantOutputUse case
'full'Feb 25, 2026Detail views, exports
'short'Feb 25Tables, cards (same year)
'relative'2 days agoActivity feeds, comments
'compact'2d agoTight UI (search results)
All variants use en-US locale. No more accidental en-GB.
Problem: Five different date-formatting functions scattered across four files, each producing slightly different output. One function accidentally uses 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.
9
Empty States
5 different empty-state patterns with inconsistent padding, color, and structure → 1
BEFORE — 5 variations, all slightly different
No clients found
text-[#a3a3a0] py-8 text-center text-sm py-8 (32px)
No requests yet
text-[#a3a3a0] py-4 text-center text-sm py-4 (16px) — half!
No deleted documents
text-sm text-[#a3a3a0] py-6 text-center py-6 (24px)
No results found
text-sm text-[#737370] py-6 text-center DARKER color (neutral-500)
No tasks
Drag tasks here
icon + text-xs font-medium + subtext text-xs text-[#d1d1ce] unique layout
AFTER — 1 consistent EmptyState component
No clients found
Try adjusting your search or filters
No documents yet
Upload your first document to get started
Upload document →
No tasks
Drag tasks here to assign them
icon (optional) + message (text-sm font-medium text-neutral-600) + subtext + optional action link. Always py-8, always centered.
Problem: Five different empty states with three different vertical paddings (py-4, py-6, py-8), two different text colors (#a3a3a0 vs #737370), and one unique icon-based layout. The visual rhythm changes unpredictably. Fix: One <EmptyState> component with consistent padding (py-8), a single text color (neutral-600), optional icon, optional subtext, and optional action link.
10
Modals
4 different modal styles with mixed corners, shadows, widths, and borders → 2 standard sizes
BEFORE — 4 modal styles, each structurally different
AFTER — 2 standard sizes with consistent structure
Standard (max-w-md / 448px)
Edit Matter×
rounded-xl shadow-xl overflow-hidden · header | body | footer
Wide (max-w-3xl / 768px)
Agent Execution Log×
Same structure, just wider. Both use rounded-xl + shadow-xl + overflow-hidden.
Consistent 3-part structure: header (title + close) | scrollable body | sticky footer with actions
Problem: Four modal variants with conflicting styles — some have rounded corners, some do not. One has a border on top of the shadow. One uses shadow-2xl and max-w-5xl (1024px wide). One puts padding on the container instead of on inner sections. Fix: Two standard sizes (Standard at max-w-md, Wide at max-w-3xl), both sharing the same structure: rounded-xl shadow-xl overflow-hidden with a 3-part layout (header, body, footer).