Make it theirs
Nothing here breaks if you skip it, which is exactly why it gets skipped. Set all five before you hand the app over.
App name
The browser tab, bookmarks, and the card when someone pastes the URL into Slack.
lib/app-config.ts → APP.nameTagline
Under the name in the app bar, and the meta description on link previews.
lib/app-config.ts → APP.taglineTheir domain
Puts the customer's own logo top-left instead of a generated square.
lib/app-config.ts → APP.domainThe accent
Take it from their brand. The same blue on every org is the clearest tell.
globals.css → --primaryTab icon
Replace with their real mark — it carries the placeholder accent right now.
src/app/icon.svgThe three planes
Depth is the system, not a finish. Everything sits on exactly one of three planes, and which one is not a matter of taste. The test: do I put something into this, or do I press it? Things you press rise; things you fill sink.
Comes toward you. Anything you actuate, and anything floating above the page — buttons, switches, chips, panels, menus, modals, toasts.
Sits in the page. Rows, table cells, body text, ghost buttons — anything that is only there to be read.
Goes into the page. Anything that receives content — text inputs, search, wells, chart stages, progress tracks.
raised* / recessed / pressable classes rather than picking shadows by hand.Depth & glass
Four steps of raised, one of recessed. Each is a rim light plus shadows at several radii — a single drop shadow reads as a sticker.
rows, chips, quiet marks
.raised-smbuttons, tiles
.raisedpanels, cards
.raised-panelmenus, modals, toasts
.raised-overWells, chart stages, empty regions, anything set into the page.
Floating surface
Sticky headers, HUDs and overlays. The app bar above uses this. Over a flat white page it does nothing.
Colour
One accent, and it is the only thing that gets to be bright. The blue below is a placeholder — take the real one from the customer's brand. Identity hues are for people; state colours mean state, never decoration.
--primary--success--warning--destructivebackgroundcardsecondarysunkenforeground — body
muted — secondary
subtle — metadata
--id-1--id-2--id-3--id-4--id-5--id-6Type
One family, medium weights, tight tracking on headings. Numbers are tabular so columns line up.
Page title · 3xl medium
Section · lg medium
Body · sm. The size most of the app is set in.
Secondary · sm muted.
Eyebrow · uppercase, tracked, above a group1,284,000 · 12.4% · tabular-nums
Fields
Recessed, with no border — the inset shadow is the edge. Errors go under the field that failed, on blur. A toast for a field error is a dead end.
As it appears on the lease.
Shown on the company's timeline.
Tabs
Two shapes, one primitive — with real arrow-key navigation and a real tablist. Never hand-roll tabs out of buttons.
Overlays
Everything opens from where you clicked, floats on the top plane, and nothing gets clipped. All of these portal, trap focus, close on Escape and restore focus to the trigger.
Feedback
Say what happened, and offer the way back. Undo beats a confirmation dialog almost every time.
Enrichment runs nightly
12 contacts are missing an email
State
Colour never carries meaning alone — the label does, and the dot reinforces it, so it survives greyscale and colourblindness.
People
Initials in the identity hue, on no coloured plate. A coloured tile behind two letters is the most generated-looking thing you can put in a list.
Data
Numbers are the main content. Tabular figures, explanations attached to anything derived, and charts you can interrogate.
| Company | Stage | Fit | Last activity |
|---|---|---|---|
| Qualified | |||
| Contacted | |||
| New | |||
| Qualified | |||
| New |
useCrossFilter. Two views that don’t talk to each other are two chances to reach different conclusions.Empty & loading
The two states nobody designs and everybody sees. An empty screen is an invitation to act; a skeleton is shaped like what is coming.
No companies yet
Import your book or add the first company, and it will show up here with its growth signals.
src/components/command-menu.tsx