Principles
The north star, the design principles, the named rules every screen is judged against, and what Bridge never does.
The trusted address book
Bridge’s north star. Every screen should look like the address book of someone everyone trusts.
Picture it. Every name in its place, with how you know each person written beside them. Kept neat and calm, nothing in the margins that isn’t useful, and one clear next step: the intro you’re about to make.
That is the whole visual idea of Bridge: people first, how you’re connected beside them, frosted glass over a soft background, dense but quiet pages, and one blue button for the step that matters. Nothing shouts, nothing is there for decoration, and you find who you need at a glance.
The address book, live
Pick a person. Blue appears only on the chosen row and the one button.
People you know
4 of 1,284 contacts
| In the address book | In Bridge |
|---|---|
| Every name in its place | A name, initials and what they do lead every row. Bridge is about people, so people are the content. |
| How you know them, written down | Degree (1st, 2nd), mutual connections and who can introduce you, as tags with words, never color alone. |
| Kept under glass | Frosted glass cards over a soft dotted background. Never a plain white box, never glass inside glass. |
| Neat pages | 13px rows with lines between them, numbers that line up, nothing in the margins that isn’t information. |
| One next step | One blue button per group, usually the intro. Everything else is quieter. |
Design principles
How the system looks and behaves. When two good options compete, these break the tie.
Consistency over creativity
Bridge is built from a system, not page by page. A familiar pattern beats a clever new one: people learn a control once and trust it in every app. When a screen seems to need something new, the system grows first.
Tag for every label, in every app. It looks and works the same everywhere.Calm, dense, scannable
Bridge is for busy people with large networks. Pages show a lot, quietly: small type, tight rows, lines instead of boxes, and one blue button per group, so the eye finds the next step without reading.
| Measure | Value | Where |
|---|---|---|
| Body text | 13px | Rows, card copy, descriptions |
| Metadata | 12px | Dates, counts, helper text |
| Card title | 15px bold | 14px in a header strip |
| Page title | 22px bold | With a 14px subtitle under it |
| Buttons | 32px | The default (sm); xs 28px, md 40px, lg 48px |
| Between cards | 16px | Every grid of cards |
| Inside a card | 24px | 20px on dashboard and feed tiles |
| Page widths | 1600 / 1280 / 800 / 720px | Tables, dashboards, reading, forms |
Fewer decisions, not more
Every rule here exists so nobody has to decide it again. If you’re choosing a padding, a color or a corner by eye, the system already has the answer, or should.
size="sm". Height, padding and corners come with it.h-[34px] px-3 rounded-[10px] by eye, a little different on every screen.Honest data
A number on screen matches the list it counts. A card that couldn’t load says so and offers Try again; it never vanishes or shows a silent zero. Each card owns its loading, empty and error states, so the rest of the page keeps working.
One card, three states
The frame stays; only the inside changes.
Loading
Empty
No contacts yet
Connect an account and your contacts show up here.
Error
Couldn’t load your contacts
Your intros
Couldn’t load your intros.
Your intros
0
Glass and quiet motion
Bridge’s surfaces are frosted glass over a soft background, and motion is quiet: things arrive, buttons press, nothing performs. Hover changes color in 150ms and a pressed button scales to 98%.
Upcoming meetings
- Leah Okonkwo, 10:00 AM
- Priya Natarajan, 2:30 PM
Upcoming meetings
Everyone can use it
Every screen meets WCAG 2.2 AA: it works with a keyboard and a screen reader, at 200% zoom, in light and dark, and on a 390px phone.
| Measure | Value |
|---|---|
| Text contrast | 4.5:1, checked on every build |
| Smallest thing you can click | 28px |
| Focus ring | 2px, with a 2px gap, on every control |
| Smallest text | 11px, except the 8px Badge sticker |
Named rules
23 rules, each named so a review can point to it. Every page lists the ones that govern it, and agents get them word for word.
Components
One of each
Bridge has one button, one label, one menu, one tooltip, one confirm dialog and one table. Before building a control, look for it here. Never hand-make a second version (a <button> with pill classes, a <span> chip, a new kebab), even if it looks the same today: copies drift.
Explained in All components
The look comes from props
A component's appearance comes only from its own options (variant, size, color). className on a package component is for layout (mt-4, shrink-0, w-full), never for font size, color, padding, radius or hover. If the look needs a className, the component or the chosen option is wrong: fix that instead.
Explained in Button
Fix the system first
If the system can't express something a screen genuinely needs, extend the component (a new size, a new option) and document it in the same change, then use it. Never work around a gap at the call site. A 1–2px change from the old design is the expected cost of joining the system, not a reason to stay hand-made.
Explained in Button
Color and surfaces
Colors are tokens
Components use role tokens only: bg-[var(--surface-secondary)], text-[var(--text-muted)]. Never hex, rgb() or Tailwind palette classes such as bg-blue-500, and never a dark: patch to fix a color: if dark mode looks wrong, fix the token.
Explained in Design tokens
Fill with -solid
Anything with white text on a colored fill uses --primary-solid or --danger-solid. The brand --primary and --error are for text, icons and focus rings; as fills they fail contrast (white on --error is 4.41:1).
Explained in Button
Never by color alone
Status and meaning are never carried by color alone. A colored tag has a word; a status color comes with an icon or text; a chart says in words what it shows.
Explained in Tag
Glass, once
Cards, panels, empty states and the navigation are frosted glass over the page background, never flat white boxes. A glass card never sits inside another glass card: inside a card, group things with dividers or a light bordered box.
Explained in Elevation
Labels are boxes, controls are pills
Tags and badges have 4px corners and never become pills. Pills (rounded-full) are for things you press: buttons, filter chips, search fields, segmented controls.
Explained in Tag
Data and layout
The frame stays
A card never disappears because its data is missing. Loading, empty and failed are three bodies inside the same frame (icon, title and hint stay put), and a failure offers Try again. Returning null on error deletes the feature from the page: the person can't tell broken from empty.
Explained in DataState
Each part loads on its own
The page frame shows straight away; each card, table or list shows its own skeleton, then its data. Never hold the whole page behind one if (isLoading) return <Skeleton />: a slow part would block the fast ones.
Explained in DataState
Reserve the space
Nothing that arrives later may push content down. A skeleton has the same height and shape as what it stands for, measured, not guessed. A visible jump after the first paint is a bug.
Explained in DataState
Counts match what's shown
A number on screen comes from the same filtered list that renders the items, never from a separate count that can disagree. Never show a silent 0 when a count failed to load.
Explained in DataTable
Words and type
Sentence case
Buttons, titles, headings, tabs, labels and statuses use sentence case: only the first word and names get a capital. "Make intro", "Intro forwarded", "Stop sharing".
Explained in Writing
Nothing below 11px
No text is smaller than 11px, except the 8px uppercase Badge sticker. Use the scale: 24, 22, 16, 15, 14, 13 (body), 12, 11.
Explained in Typography
Numbers line up
Numbers that change or get compared (stats, counts, dates in a column, page numbers) use tabular-nums, so digits keep their width and columns line up.
Explained in Typography
One way to write dates
"Today", "Yesterday", "4 days ago" under 7 days; then "Jul 20", and "Jul 20, 2025" in another year. Times are 12-hour (9:40 AM), in the viewer's own time zone. Use formatDate and formatTime from @brdgapp/ui/lib/format.
Explained in Writing
Say what happens
Buttons start with a verb and name the object: "Stop sharing", not "OK" or "Submit". A confirm dialog asks a question about the specific thing and its button repeats the verb. Errors say what happened and what to do next.
Explained in Writing
Access and motion
Every control has a name
Every control says what it does to a screen reader. An icon-only button carries an aria-label naming the action ("Close", not "X"); a group of filters or a table has a name; a tooltip is never the only name.
Explained in IconButton
Focus always shows
Every control shows a visible ring when reached by keyboard (focus-visible). Never remove an outline without drawing a ring in its place.
Explained in Accessibility
Quiet motion
Motion marks arrivals and presses, not decoration. Respect reduced motion: CSS animations are cut by the stylesheet, and JavaScript animations check useReducedMotion(). Loading spinners keep turning.
Explained in Motion
Other
One icon set, one icon per meaning
Every icon is Lucide at its own 2px line. Each meaning has one icon, listed on Iconography; a meaning that isn't listed gets a Lucide icon nobody else uses, added to the list in the same change. No other icon set, no emoji in the interface, and no hand-drawn company logos: BrandMark draws them.
Explained in Iconography
What Bridge never does
Never right in Bridge, whatever the screen. Each breaks a named rule.
- A hand-made button, chip or menu. Use Button, Tag or ActionMenu. One of each
- Hex, rgb() or palette classes in a component. Use a role token. Colors are tokens
- A
dark:class to fix a color. Fix the token instead. Colors are tokens - Restyling a package component with className. Pick the variant and size. The look comes from props
- Filling a button with
--primaryor--error. Use the -solid tokens. Fill with -solid error ? null. Keep the frame; show the error body. The frame staysif (isLoading) return <Skeleton />for a whole page. Let each part load on its own. Each part loads on its own- Content that pops in and pushes the page down. Reserve its space with a same-height skeleton. Reserve the space
- Glass inside glass. Use dividers or a light bordered box inside a card. Glass, once
- The browser's
titleattribute as an explanation. Use InfoHint or Tip. One of each window.confirm. Use ConfirmDialog. One of each- Buttons labeled OK, Yes, Submit or Click here. Name the action. Say what happens
- "Are you sure?" as a dialog title. Ask about the specific thing. Say what happens
- "Oops", emoji or error codes in user-facing errors. Say what happened and what to do. Say what happens
- Text below 11px (other than the Badge). Use the type scale. Nothing below 11px
- Title Case in the interface. Use sentence case. Sentence case
“Are you sure?”
Stop sharing with Leah Okonkwo?
Leah will no longer see your network. You can share again at any time.
Are you sure?
This action cannot be undone.
“Oops”
Couldn’t load your intros. Try again.
Oops! Something went wrong 😕 (ERR_500)
Title Case
A red button from the wrong red
variant="destructive". Its --danger-solid fill keeps white text readable in both themes.--error. White on it is 4.41:1, under the 4.5:1 minimum.Using the rules
The rules are short so they can be quoted. “This breaks Reserve the space” is faster than a paragraph of taste.
| Where | What it does with them |
|---|---|
| This page | Lists every rule, grouped by area, with the page that explains it. |
| Each handbook page | Repeats the rules that govern it, at the end of the page. |
| Agents | Get every rule word for word through the MCP server and /llms.txt; the code checker flags breaks. |
| Reviews | Name the rule a change breaks. If a rule is wrong, change it here, not on one screen. |
When the code and the rules disagree, DESIGN.md and these rules win, and the code gets fixed. Agents start at For agents.