BridgeHandbookDraft
For agents
Menu
Stable

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

Leah Okonkwo chosen
In the address bookIn Bridge
Every name in its placeA name, initials and what they do lead every row. Bridge is about people, so people are the content.
How you know them, written downDegree (1st, 2nd), mutual connections and who can introduce you, as tags with words, never color alone.
Kept under glassFrosted glass cards over a soft dotted background. Never a plain white box, never glass inside glass.
Neat pages13px rows with lines between them, numbers that line up, nothing in the margins that isn’t information.
One next stepOne 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.

1stAcceptedFundraising
Do. Use Tag for every label, in every app. It looks and works the same everywhere.
1STaccepted!Fundraising
Don’t. Draw a one-off chip because this screen is “different”.

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.

MeasureValueWhere
Body text13pxRows, card copy, descriptions
Metadata12pxDates, counts, helper text
Card title15px bold14px in a header strip
Page title22px boldWith a 14px subtitle under it
Buttons32pxThe default (sm); xs 28px, md 40px, lg 48px
Between cards16pxEvery grid of cards
Inside a card24px20px on dashboard and feed tiles
Page widths1600 / 1280 / 800 / 720pxTables, dashboards, reading, forms
Do. Give the group one blue button, the step it exists for. Everything else is secondary or ghost.
Don’t. Make every action blue. When everything is the next step, nothing is.

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.

Do. Pick a size: size="sm". Height, padding and corners come with it.
ExportFilterMake intro
Don’t. Set 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

Your intros

Couldn’t load your intros.

Do. Say it couldn’t load, inside the card, and offer Try again.

Your intros

0

Don’t. Show 0 for a number that failed. People will believe it.

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
Do. One glass card; split its inside with lines or a light bordered box.

Upcoming meetings

Leah Okonkwo, 10:00 AM
Priya Natarajan, 2:30 PM
Don’t. Put glass inside glass. The edges stack up and the hierarchy blurs.

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.

MeasureValue
Text contrast4.5:1, checked on every build
Smallest thing you can click28px
Focus ring2px, with a 2px gap, on every control
Smallest text11px, except the 8px Badge sticker
Accepted
Do. Put a word next to every status color.
Don’t. Show status by color alone. Not everyone can tell green from red.

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

Share at two

A component moves into the package once two places in one app need it, or as soon as a second app does. A near-copy becomes an option on the existing component, not a second component.

Explained in All components

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

One blue button per group

In each group of actions, only the single most important one is a primary (filled blue) button. The others are secondary, ghost or link. When everything is blue, nothing stands out.

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 --primary or --error. Use the -solid tokens. Fill with -solid
  • error ? null. Keep the frame; show the error body. The frame stays
  • if (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 title attribute 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.

Do. Ask about the specific thing, say what changes, and repeat the action on the button.

Are you sure?

This action cannot be undone.

Don’t. Ask “Are you sure?” with Yes and No. Nobody can tell what they’re agreeing to.

“Oops”

Couldn’t load your intros. Try again.

Do. Say what failed and what to do, in two short sentences.

Oops! Something went wrong 😕 (ERR_500)

Don’t. Use “Oops”, emoji or error codes. People still don’t know what broke.

Title Case

Do. Sentence case: only the first word and names get a capital.
Don’t. Title Case. It reads louder and breaks the calm voice.

A red button from the wrong red

Do. Use variant="destructive". Its --danger-solid fill keeps white text readable in both themes.
Stop sharing
Don’t. Fill with --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.

WhereWhat it does with them
This pageLists every rule, grouped by area, with the page that explains it.
Each handbook pageRepeats the rules that govern it, at the end of the page.
AgentsGet every rule word for word through the MCP server and /llms.txt; the code checker flags breaks.
ReviewsName 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.