Design system · Foundation v0.1
Precise enough for data. Human enough for decisions.
Current evolves Wave into a quieter, denser system for Surfe’s GTM intelligence platform—built for context, confidence, and momentum.
Intent stays visible
Prompts resolve into inspectable filters, fields, and actions.
Density without noise
Compact GTM data is structured through rhythm, not decoration.
Confidence by design
Source, freshness, cost, and progress sit beside the decision.
Foundations / Color
Brand energy, used with restraint
Wave’s petrol, blue, orange, and purple remain recognizably Surfe. Semantic roles keep saturated colors meaningful in a data-dense product.
Blue
Brand, actions, focus and data
50
#EFFBFE100
#EBFAFE200
#D1F2FD400
#88D7F0500
#73C7E1600
#1798C1650
#0F82A7700
#387082800
#073742900
#08272EGrey
Canvas, hierarchy and borders
50
#FAFAFA100
#F4F5F6150
#EBEFF0200
#E0E5E6300
#C1CACD400
#889BA0450
#5C818A500
#5A6C71600
#49575B700
#3A4446800
#22282A900
#15191ASemantic
Feedback, AI and brand warmth
Green 100
#ECFBF3Green 500
#00BD9DGreen 600
#068570Orange 100
#FEEFE6Orange 500
#FFB554Orange 600
#E5661EOrange 700 · warning
#B4470FPurple 100
#EFE7FFPurple 500
#AF8AF5Purple 600
#7A5AB8Pink 100
#FFF1FCPink 500
#FC84E1Pink 600
#D160B7Red 100
#FFEBEERed 500
#F26379Red 600
#D64D62Yellow 100 · accent only
#F8FABBYellow 500 · accent only
#EBD619Yellow 700 · accent only
#7D720EFoundations / Typography
One family, deliberate hierarchy
Inter keeps parity with Wave while a compact product scale, tabular numerals, and tighter headings improve analytical scanning.
36 / 40The clearest path to your next customer24 / 32Companies showing buying intent16 / 24Enrichment coverage14 / 20Review the fields Surfe will enrich before using credits.12 / 16WORK EMAIL · VERIFIED11 / 16UPDATED 4 MIN AGOFoundations / Scale
A complete, predictable spatial system
A 4px base supports compact controls and spacious compositions. Named control and panel sizes prevent one-off dimensions.
Spacing · 4px base
14px28px312px416px624px832px1248px1664pxRadius & elevation
Radius scales with the box: small radii express precision, large radii belong to panels and major compositions. Depth is a hairline plus a background step — a tray tucks behind its surface instead of earning a border, and the single shadow tier is reserved for layers that float above the page.
Hierarchy & rhythm
Category title
Sits on the canvas, never inside the card.
Row label
Supporting line
Row label
Supporting line
- The card is the category. One card holds one category, and its edge is the only grouping device. A row inside it never repeats the category’s weight.
- Three tiers. Page title 24px semibold, category title 16px semibold on the canvas, row label 14px medium in the card. Each tier is a size step and a weight step from the next, so no rule or indent is needed to read the structure.
- Space carries the grouping. 48px between categories, 12px from a title to its card, and rows at 64px. A title is always closer to its own card than to the category above it.
- Warning is orange. Yellow is a brand accent and never a state: warnings, scheduled changes and amber meters all read from `--warning`.
Foundations / Motion
Weight, not decoration
Arrivals ease out, departures ease in fast, things the user throws get a touch of spring. Frequent actions do not animate. Reduced motion keeps the information and drops the travel.
Durations
--motion-instant0msKeyboard-driven changes, command palette--motion-fast100msHover tints, pressed states, tooltips, checks--motion-base160msPopovers, menus, tab indicator, toasts--motion-slow240msDialogs, drawers, row reorder--motion-deliberate400msValue arrival glow, number count-up--motion-celebrate600msMilestones only
Easings
Click a row to run it at the deliberate duration.
Signature interaction · value arrival
Shimmer while pending, then a crossfade with an accent glow that decays over 1.2s. The button keeps its width and label while loading.
Controls · spring on the things you throw
Check pops with the snappy spring, the switch thumb travels with the soft spring. The button press lands instantly, half a pixel down with a tint, and only the release lifts over 100ms. No spring: frequent actions get feedback, not motion.
Foundations / Appearance
Things come from somewhere
Nothing appears at full size from nowhere. Popovers scale from their trigger, drawers slide from their edge, lists reveal once at 20ms per item and never again. Everything here is a token — no animation library runs on the hot path.
Arrival animations
fade-inbase · 160ms
Page and region crossfades. No travel, no scale.
scale-inbase · 160ms
Popovers and menus, growing from the trigger’s side at 0.96.
rise-inslow · 240ms
Cards and rows on first paint. 6px of travel, never more.
pop-inbase · snappy
A badge or count that changes, with the snappy spring.
slide-in-rightslow · 240ms
Drawers and rails, travelling from their own edge.
slide-in-bottomslow · 240ms
Sheets on narrow viewports, and the proposal card.
Every arrival eases out; every departure eases in one duration token faster. Scale never starts below 0.96 for overlays or 0.9 for a badge, and slides travel 4–8px — except drawers, which travel their own width.
Stagger · first paint only
20ms per item, capped at six, and only the first time a list paints. Never on reorder, never on filter — a list that re-staggers on every keystroke reads as slow, not as alive.
Ongoing animations
shimmer · skeletons
breathe · the assistant is thinking
glow-decay · an enriched value lands
Shimmer says a placeholder is waiting. Breathe says one thing is thinking — one dot, at the deliberate cadence, never a row of bouncing dots. The glow decays over 1.2s so the eye finds what changed in a 10,000-row grid.
Celebration · rare by design
A ripple in the accent colour, once per milestone, then a static check and one line of warm copy. Four moments qualify: first list saved, the first enrichment that finds a missing email, a bulk run over 100 rows finishing clean, and credits topped up. No confetti, no sound, no modal.
Foundations / Loading
A skeleton per kind of thing
A skeleton is not a grey box. It is a stand-in for one specific element at that element's exact size, so content lands without a shift and the shape tells the reader what is coming. Flip the switch: nothing below should move between the two states.
The eight variants
textA line of body copy
headingA title line
circlePerson avatar, icon button, status dot
rectCompany logo, thumbnail, media box
blockA card body, a chart, a map
chipBadge or filter chip
controlButton or input, keeping the toolbar height
cellInside a data cell
Each one carries the geometry of what it replaces: text is a cap-height line, circle is a person, rect is a logo, control keeps a button's 32px so a toolbar never reflows. Nothing here is a rectangle by accident.
List rows
SkeletonListRow reads the same --density-row as ListRow, so a list of placeholders is exactly as tall as the list that replaces it. Media, description and meta are opt-in per surface.
Key–value pairs
The term line is short and lighter, the value line is long. A panel of these reads as a record before a single value has arrived.
Table rows
DataTableSkeleton takes one width per column, so the header never jumps sideways when the body fills. The header, toolbar and footer stay live the whole time — a full-page skeleton for a shell the user already saw is a failure.
Toolbars
Controls hold their height so the surface below never slides up. The search field, the filter chips and the trailing action each keep their own footprint.
Cards and panels
A card skeleton mirrors its own anatomy: identity header, body copy, footer actions. It is built from the same slots as the card it replaces, which is why the two line up.
Charts and long copy
Bar heights are fixed, never random, so the placeholder never implies a shape the data does not have — and so the server and the client render the same thing. Paragraph skeletons end on a short line, the way real text does.
Rules that make them work
- Show nothing below 200ms — a flash of skeleton reads slower than a blank.
- Once shown, hold for at least 300ms so it cannot flicker.
- Match the final layout exactly, then crossfade. Nothing jumps.
- Never skeleton a shell the user has already seen — refetch behind a hairline progress bar instead.
- One SkeletonRegion announces the wait once; the boxes themselves are aria-hidden.
- Fixed dimensions only, never randomised widths: the server and client must agree.
- Shimmer, not pulse — and under reduced motion the wave stops while the shape stays.
- A busy control gets a Spinner. A loading region gets a skeleton. Never the reverse.
Break any one of these and the skeleton reads slower than a blank space.
Avatars
People stay round, companies stay square, at all four sizes — so even the placeholder says which kind of record is arriving.
Primitives / Actions
Every state a control can be in
Six variants and four sizes, then the states that decide whether a control is trustworthy: loading keeps its width, disabled keeps its shape, and pressed lands in the same frame as the click.
Variants
Petrol is the one primary. Brand blue carries progressive actions, soft and ghost carry utilities. Two primaries in one region is a review failure.
Sizes
24 / 28 / 32 / 40px, from inside a cell to a hero action. Icons scale with the control: 14 / 16 / 16 / 20px.
States
Loading swaps the leading icon for a spinner, keeps the label and the width, sets aria-busy and blocks re-entry. It never shrinks to a bare spinner.
Icon-only and groups
An IconButton always carries a label: it becomes the aria-label and the tooltip. ButtonGroup attaches related actions; the split button is a group whose second child opens a menu.
Links
Amelia was added from UK RevOps leaders four days ago.
Standalone links look like actions, inline links stay underlined inside a sentence, and external links carry the arrow plus rel=noreferrer.
Keyboard hints
Shortcuts are visible where the action is: in menus, in icon-button tooltips, and in the ? dialog. The modifier glyph resolves per platform at render, never hard-coded.
Primitives / Forms
Fields that prevent the error
A Field owns the label, the description, the error and the control's ids, so a control is never labelled by placeholder text. Invalid state travels up from the input through has-* — nothing passes an isInvalid boolean around.
Input · sizes and states
Placeholder text never replaces a label. Read-only is muted but selectable; disabled is not. The loading spinner sits in the trailing slot so the field keeps its width.
Search and numbers
Reported value: —
Employee range · 50–500
Search echoes the keystroke immediately and reports the value 150ms later; Esc clears. Number fields format on blur, and arrows step — Shift steps by ten.
Field anatomy
Shown in the sidebar and in the assistant’s context.
Label, description, control, error. The error replaces nothing — it is added below, so the layout does not jump when validation runs.
Selection
Select-all is a parent checkbox with a real indeterminate bar, not a dimmed check. Radio cards carry a description, so a choice is made once.
Long-form and actions
The textarea grows with its content through field-sizing, capped before it swallows the dialog. FormActions fixes placement: destructive far left, confirm always last on the right.
Primitives / Display
Everything that shows a value
Text and Heading keep the ramp finite. Badge, Chip and StatusDot share one tone set, and every one of them pairs colour with a word or a glyph, so status survives a colour-blind reader and a greyscale print.
Text and Heading
Companies showing buying intent
Enrichment coverage
Work email
Body · the reading size in panels and forms.
Muted · secondary lines and metadata.
Subtle · the quietest tier, for timestamps.
Updated 4 min ago
12,340 rows · 1,284 credits
Truncation · Northwind Analytics International Holdings and Subsidiaries Limited
Clamp · Amelia owns the RevOps tooling budget for EMEA and has been evaluating replacements for their current enrichment vendor since the start of the quarter.
Visual level is decoupled from the semantic tag: a card title can be an h3 that looks 14px. Numbers get tabular figures so columns of them align.
Badge · variant × tone
Soft is the default in dense surfaces, solid is for a single emphatic state, outline is for a badge sitting on a tinted background. Counts cap at 99+ so a badge never resizes its parent.
Chip
Filter chips read field · operator · value and open their editor on click. Backspace removes from the keyboard; the × removes from the pointer. Long values truncate rather than wrap the bar to a third line.
Identity
People are round, companies are square — the shape is the type. The tint is derived from the record id, so the same person keeps the same colour on every surface. A failed logo falls back to a monogram without shifting the row.
Status, progress and credit pools
All pools
A meter changes tone at 20% and again at empty, so a low balance is seen before it is read. Progress only ever moves forward.
Loading and iconography
Skeletons match the final layout exactly and shimmer rather than pulse. Spinners belong on controls, never in place of a region skeleton. Icons come through Icon at 14/16/20/24 with stroke 1.75.
Description list · the honesty ladder
- Work email
- amelia@northwind.io
- Mobile phone
- EnrichingEnriching
- Direct dial
- CRM status
- Couldn’t fetch
Every value states which of the five statuses it is in. Missing offers the enrich affordance with its cost, unavailable explains why in a popover, failed offers retry. A blank cell is never acceptable.
Primitives / Feedback
Say what happened, what it means, and what to do
Empty states name what will be here and offer the first step. Errors never blame the user and never lead with a status code. One alert per region — a stack of banners is a design failure, not an information architecture.
Empty · page
No lists yet
Start from a search, a prompt, or a blank list. Whatever you build stays yours.
The wave motif appears only here and at milestones: brand gradient at low opacity, never a character.
Empty · panel and cell
No people match these filters
Seniority VP+ in the United Kingdom returns nothing at this company size.
Nothing here yet
The no-results variant offers the action that would widen the search, not a shrug. In a cell there is no illustration and no button.
Error · retryable and terminal
Couldn’t reach Surfe
Your list is safe and nothing was charged. Retry now, or we’ll retry in a minute.
req_8fd2c41aThis list was deleted
Someone on your workspace removed it two hours ago. Its rows are in the workspace archive for 30 days.
Three sentences maximum. The correlation id is there for a support conversation, small and out of the way.
Inline alert
118 email credits left
3 rows failed to enrich
Viewer access
Only the danger tone announces itself to assistive tech. Dismissal is the user’s call — an alert never times out on its own.
Primitives / Overlays
Modals, menus and everything that floats
Things come from somewhere: a popover scales from its trigger, a sheet slides from its edge, a dialog fades and scales from 0.98. Menus opened by keyboard do not animate at all — the frequency law is a speed rule as much as a taste rule.
Dialog · a decision
Backdrop dims to 40% and blurs slightly; the page behind is inert and cannot scroll. Focus starts on the first field and returns to the trigger on close. A dialog never opens another dialog.
Alert dialog · the irreversible
Confirmation is reserved for what undo cannot fix. Initial focus is on Cancel, and above 50 rows the confirm needs the list name typed. Everything reversible skips this and gets an undo toast instead.
Sheet · work, not a decision
A sheet that holds work is non-modal and does not dim the page; one that holds a decision is modal. Both slide from their own edge over 240ms and leave over 160ms.
Menus
Destructive items sit last, after a separator, and take the danger tone only on hover and focus. Shortcuts are visible in the menu, not learned elsewhere.
Tooltip, popover, preview card
A tooltip waits 400ms, then siblings open instantly. A preview card waits 300ms and is never modal. A popover closes on outside click, on Esc, and when its trigger scrolls away.
Command palette
Command Palette
Search for a command to run...
The palette is always one chord away, and it never animates: it is the most frequent action in the product, so it shows and filters instantly.
Disclosure
Tabs switch peer views: the indicator slides, the content swaps instantly. Accordion and collapsible heights interpolate to auto, so a section can grow without a measured pixel value.
Data / Tables and lists
The table under real pressure
Row height, cell padding and control gaps all read from --density-*, so one attribute moves the whole surface. Selection tints instantly, hover is a tint and never a lift, and every one of the four body states preserves the column widths so content lands without a shift.
Column stress
Person· auto width, truncate with tooltipCompany· auto width, truncate with tooltipWork email· auto width, truncate with tooltipEmployees· fixed width, right-aligned, tabularFit· fixed width, badge from a colour map plus a word
A 200-character company name truncates rather than widening the column. Numbers are right-aligned with tabular figures so the digits line up down the column. Actions appear on hover and on focus — never hover alone, or a keyboard user cannot reach them. Current density: compact.
Stacked list
Pinned
All lists
The same row shape as the picker, the feed and the sidebar's saved lists. Group headers stick while their group scrolls. Past 100 items this is the seam where virtualisation goes in.
Primitives / Input
Inputs and filters
Quiet at rest, unmistakable in focus. Labels carry meaning; placeholder text never replaces instructions.
Used for enrichment previews only.
Primitives / Status
Badges and identity
Status color is always paired with language. Avatars stay calm and readable inside dense surfaces.
Semantic badges
Identity
Primitives / Navigation
Tabs
Tabs switch peer views, not sequential steps. The active state remains legible without a heavy container.
Patterns / Enrichment
Context before action
Identity, available data, provenance, and credit impact live in one inspectable decision surface.
Amelia Morgan
MatchVP Revenue Operations · Northstar Labs
Work email
amelia@northstarlabs.com
Company size
201–500 employees
Location
London, United Kingdom
CRM status
Not in HubSpot
Surfe assistant
Working with this profile
Patterns / Data
A list row under real pressure
Column rhythm, selection, provenance, enrichment, and actions are tested together at realistic density.
| Person | Company | Work email | Fit | Actions | |
|---|---|---|---|---|---|
AM Amelia Morgan VP RevOps | Northstar Labs | amelia@northstar.io | High | ||
JL Jonas Lee Revenue Director | Amplitude | High | |||
SO Sarah Okafor Head of GTM | Census | sarah@census.com | Medium |