AcuaDesign system
Corporate brandAIv2 · 2026-08

Quiet, dense, and square.
Built from parts we don't maintain.

This is how Acua's product UI is put together. It is not a component library — it is a set of decisions about which off-the-shelf parts to use and how to use them, plus rules for the few parts we made ourselves. The reference bar is Linear-level precision, Attio-level density, Vercel-level restraint.

Ships first at demo.acua.aiSource of truth: the running product
01

Principles

Five decisions that settle most arguments before they start.

  1. 01
    Ink does the work

    One accent: near-black. Color is reserved for status and data — never for decoration, never for hierarchy.

  2. 02
    Structure over chrome

    Hierarchy comes from type, spacing and 1px borders. If a card can be a border, it's a border. If a border can be whitespace, it's whitespace.

  3. 03
    Dense but calm

    Operators live here for hours. Compact rows, small controls, restrained copy — and enough air that nothing shouts.

  4. 04
    The table is the product

    Most screens are a list being worked. Toolbar, columns, alignment and footer are first-class design surfaces.

  5. 05
    Buy boring, own little

    Use maintained open parts unmodified. Every component we own ourselves is a liability with a named justification.

02

Use the shelf

We have no designers and no appetite for a bespoke library. For each need, this table names the part we use — as-is. Restyling happens through tokens, not forks.

NeedUseNotes
Buttons, inputs, dialogs, dropdowns, tabs, tooltips…shadcn/uiGenerated into the repo; styled only via tokens (radius 0, ink primary).
Data tablesTanStack Tableshadcn is the visual layer; TanStack is the engine (sorting, visibility, selection).
Chartsbklit (visx)The registry source lives in components/charts. No other chart library.
Command palettecmdkOne global ⌘K menu; labels reuse the sidebar's i18n strings.
Iconslucide16px in controls and nav. One library, never mixed.
TypeGeist + Geist MonoMono for document numbers, amounts, codes.
Motionframer-motionOnly where listed in section 10.

The rule: before writing any component, check the shelf. If shadcn has it, use it and move on. Our own parts are listed in section 12 — everything else is somebody else's maintenance problem.

03

Color

Neutral surfaces, ink actions, color only where it means something. The old brand navy (#142860) and blue-as-accent are retired.

Neutrals — the whole interface

gray-50
#f9fafb
gray-100
#f3f4f6
gray-200
#e5e7eb
gray-300
#d1d5db
gray-400
#9ca3af
gray-500
#6b7280
gray-600
#4b5563
gray-700
#374151
gray-800
#1f2937
gray-900
#111827
  • Canvas white · subtle panels gray-50 · borders gray-200
  • Text gray-900 · secondary gray-500 · disabled gray-400
  • Primary actions gray-900, hover gray-700

Status — the only permitted color

PaidPending approvalRejectedDraftIn review

Status renders as colored text with a small glyph — no filled chips, no loud pills. Strong red is reserved for destructive actions and real errors. Blue survives only as an informational tone and link color in prose.

Chart ramp — data only

chart-1
oklch token
chart-2
oklch token
chart-3
oklch token
chart-4
oklch token
chart-5
oklch token
chart-6
oklch token
chart-7
oklch token
chart-neutral
oklch token

Seven hues at near-constant lightness and chroma, so series carry equal weight; a series keeps its hue across every chart type. chart-neutral is what “Other” is drawn in. The ramp never appears in UI chrome.

04

Typography

Geist for everything; Geist Mono wherever a human compares digits. Type — not color, not boxes — carries the hierarchy.

Scale
Bill Pay
page title · 24 / semibold / tracking-tight
Vendor information
section · 16 / medium
The bill was submitted for approval.
body · 14 / regular
Updated 3 minutes ago
meta · 12 / gray-500
INV-2026-4581 · ฿56,710.00
ids & amounts · mono / tabular-nums
Do
  • ·Right-align money; always tabular figures
  • ·One semibold weight per screen region
  • ·Sentence case everywhere (see Writing)
Don't
  • ·Bold as a substitute for structure
  • ·Uppercase label headers (legacy)
  • ·Fluid type tricks in data screens
05

Shape & depth

Radius zero, everywhere. The only curves are circles.

Square by default
Upload billSearch bills…APApproved

border-radius: 0 on every control, panel, dialog and chart bar. rounded-full is the single exception — avatars, status dots, count pills.

Depth is 1px of gray-200. Shadows exist only on floating layers (menus, dialogs, tooltips) and even there stay barely visible. Nothing else floats.

06

Spacing & density

A 4px grid, and control heights chosen for hours of daily use.

ElementSizeRule
Toolbar controlsh-8Search, filters, icon buttons in list toolbars
Form controls & primary actionsh-9Buttons, inputs, selects in forms and page headers
Table rows~52pxTwo-line cells (name + muted meta) allowed; three never
Page gutter24–32pxOne consistent gutter per page; no nested cards
Section gap40–56pxSections separated by whitespace or a single border
07

Components

Everything below is shadcn/ui, unmodified — these examples only show which variant to pick and how it lands with our tokens.

Buttons

ApproveExportCancelVoid bill

useButton — variant="solid" color="primary" | outline | ghost | destructive

One ink-solid primary per view. Everything else is outline or ghost. Green/blue solids no longer exist — approve, confirm and pay are primary actions, so they are ink.

Inputs & pickers

useInput · Select · Combobox (searchable) · DatePicker — labels above, never placeholder-only

Dropdown menu

View
Duplicate
Send reminder
Delete

Rendered open for reference — this is shadcn DropdownMenu with system tokens.

useDropdownMenu — destructive items last, separated; icons optional at 16px

Tabs

AllDraftPending approvalPaidVoided 2

useTabs (underline variant) — counts as quiet gray chips, not colored badges

Status & badges

Ready for reviewPartially approved (1 / 2)NewAssigned to me

useStatusPill (ours, see 12) for document status · Badge soft tints for annotations

Toolbar

Search bills…

Search sits left; view controls are icon-only with tooltips and sit together at the right edge.

useToolbarIconButton (ours) — active state = ink fill + count dot; label lives in the tooltip

Overlays

Dialog

Short, focused interactions: confirm, name something, pick one thing.

Full-screen takeover

Document detail (bill, PO, receipt): covers the viewport in ~200ms; Esc closes; content keeps list context in a left rail.

Command palette

⌘K — navigation and create actions from anywhere.

Side drawers are retired for document detail — a bill deserves the whole screen. Modals never stack more than one level.

08

Tables

The main workspace. Curated default columns; everything else one click away behind the columns picker.

BillVendorTotal amountDue dateStatus
INV-2026-4581
Sukhumvit Print House Co., Ltd.
Head office
9,630.00 THB17/09/2026Ready for review
INV-2026-4562
Rayong Industrial Parts Co., Ltd.
Head office
38,841.00 THB08/10/2026Pending approval
INV-2026-4549
Chao Phraya Logistics Co., Ltd.
Head office
75,756.00 THB21/08/2026Paid
3 of 16 bills฿124,227.00

Anatomy: quiet headers · vendor cell with muted meta line · right-aligned mono amounts · status as colored text · footer with count and sum.

Do
  • ·6–9 default columns; the rest opt-in via the picker
  • ·Doc numbers in ink, underline on hover
  • ·Skeleton rows while loading — chrome never blanks
Don't
  • ·Amount columns hidden by default (ever)
  • ·Full-page spinners over a table
  • ·Uniform-width bars as loading state
09

Charts

bklit (visx) primitives from components/charts — the same source Compass uses. Restraint over decoration.

Stacked bars — the house chart
  • Square bars (lineCap 0), quiet 1px grid, muted axis labels.
  • Fills are flat ramp tokens. Gradients only on a single-series area.
  • Tooltips list series rows with a total last; center of a donut answers the question the ring asks.
  • Money ticks compact (150K); full amounts only in tooltips.
  • ~650ms enter animation, then still. Nothing pulses.
10

Motion

Animation exists to explain a spatial change, then get out of the way.

Each stage runs the real curve and duration. Replay them — the point of these numbers is how they feel, not what they read like.

Menus, tooltips, popoversfade + 2% scale · 140ms
Actions
Edit
Duplicate
Archive
Full-screen takeoverfade + 8px rise · 200ms
INV-2026-4581
Esc closes · list stays in the left rail
Chart entergrow once on mount · 650ms
Skeletonsgentle pulse · continuous
Everything elseno animation

Selection, hover and state changes snap. Nothing to wait for.

SurfaceMotionDuration
Menus, tooltips, popoversfade + 2% scale120–150ms
Full-screen takeoverfade + slight rise200ms
Chart entergrow once on mount~650ms
Skeletonsgentle pulsecontinuous
Everything elsenone
11

Writing

Sentence case, everywhere: headers, buttons, labels, tabs, menus. Acronyms and product names keep their capitals.

WriteNot
Total amountTotal Amount
Create purchase orderCreate Purchase Order
Linked PO · Linked GRslinked po
Bill Pay (product name)Bill pay
Mark as paidMARK AS PAID

Buttons say what happens (“Upload bill”, “Send reminder”) in three words or fewer. Empty states are one sentence and one action — no illustrations that outweigh the data.

12

Our own parts

The complete list of components we own. Each exists because the shelf had nothing — and each carries its rules here. Adding to this list requires the same justification.

StatusPill
components/ui/status-pill.tsx

Document status as glyph + colored text. No fill, no border. One glyph per status, ever.

ToolbarIconButton
components/ui/toolbar-icon-button.tsx

Icon-only toolbar trigger with tooltip label; engaged state fills ink and shows an applied-count dot.

TableSkeleton
components/ui/table-skeleton.tsx

Loading rows that keep table chrome in place; widths vary per row so it reads as content.

CommandMenu
components/layout/CommandMenu.tsx

The ⌘K palette. Destinations reuse sidebar i18n keys so the two never drift.

CurrencyAmountCell
purchase-management/shared/CurrencyAmountCell.tsx

Money in tables: right-aligned, tabular, converted amount stacked muted underneath.

bklit charts
components/charts/

Vendored registry source (shared with Compass). Edited like our own code; chrome pointed at semantic tokens.

Business-critical flows behind these surfaces (bill lifecycle, payment guards) are additionally modeled and proved in Lean — see verification/ in the docs repo. Design changes may not weaken a proved invariant.