Using the system
How to pick up the package in an app: imports, tokens, data states, feedback, theming, accessibility and where new code goes.
Setting up
Bridge’s components, tokens and styles ship as one package, @brdgapp/ui. An app installs it once and imports the stylesheet after Tailwind. That brings every token in light and dark, and tells Tailwind to read the package’s files.
/* The app's main CSS file, after Tailwind */
@import "tailwindcss";
@import "@brdgapp/ui/globals.css";- Needs React 18 or 19 and Tailwind CSS 4.
- Load the Mulish font the way the app loads fonts. With
next/font, name the variable--font-mulish. - The theme follows
data-themeon<html>(see Theming below). - Mount
<Toaster />once in the root layout, sotoast.success()works from any screen.
Importing
Import each part from its own file. There is no single index file to import from.
What you can import, read from the package’s package.json
| Import path | Points to | Holds |
|---|---|---|
@brdgapp/ui/components/* | packages/ui/src/components/*.tsx | One component family per file. |
@brdgapp/ui/hooks/* | packages/ui/src/hooks/*.ts | Behavior without markup. useMockQuery is for the handbook and prototypes only. |
@brdgapp/ui/lib/* | packages/ui/src/lib/*.ts | cn, the number and date formatters, ElementProps. |
@brdgapp/ui/globals.css | packages/ui/src/styles/globals.css | Every token in light and dark, the base styles and the custom utilities. |
@brdgapp/ui/package.json | packages/ui/package.json | The version, for tools. |
Typical imports
import { Button } from "@brdgapp/ui/components/button";
import { DataState } from "@brdgapp/ui/components/data-state";
import { formatCount, formatDate } from "@brdgapp/ui/lib/format";
import { cn } from "@brdgapp/ui/lib/utils";Always import cn from @brdgapp/ui/lib/utils, never clsx or a copy in the app, so a class you pass can override the component’s own.
Styling with tokens
Style with Bridge’s role tokens, never raw colors. A token names a job (--text-muted, --surface-secondary), so the same class is right in light and dark.
text-[var(--text-muted)], bg-[var(--surface-secondary)], border-[var(--border-default)]
text-gray-500, bg-white, border-[#ECEDF0]: they break in dark mode and drift from the system.
The type scale
| Size | Use for |
|---|---|
| 22px bold | Page headings and single stat numbers |
| 16px bold | Section headings |
| 15px bold | Card titles (14px in a header strip) |
| 14px | Sub-labels, tabs, buttons |
| 13px | Body text, rows, card copy |
| 12px | Metadata: dates, counts, helper text |
| 11px bold uppercase | Eyebrows only. Nothing is smaller, except the Badge sticker. |
- Every number that changes or gets compared uses
tabular-nums. Write it withformatCount,formatMoney,formatPercentorformatDate, never by hand. - A package component’s look comes from its props (
variant,size). UseclassNameon it only for layout:mt-4,w-full,shrink-0.
Every token with its light and dark value is in Design tokens; the custom utilities, such as liquid-glass, are in Styles and utilities.
Your first screen
Build a screen builds a whole Bridge OS page from the package, in the order you’d build any page: pick the app, pick the template, lay out the parts, then fill each one. Start there once the package is set up.
Data and its states
Every card, table and panel that loads data owns its own loading, empty and error states. DataState takes the query’s status and data and shows a skeleton, an empty state, an error with Retry, or your content.
A card that owns its states
Try Slow and Fail in the sidebar’s demo controls.
Recent intros
- Never hold up a whole page while one part loads. The card and its title always show; only the inside waits.
- Shape the skeleton like the loaded content, so nothing moves when the data arrives.
- An empty state says what will be here and how to fill it. An error says what failed and offers Retry.
useMockQueryis for handbook examples and prototypes. In an app, pass the real query’s status and data.
The full rules are in Loading, empty and error.
Feedback
Every action shows that it worked. If the result is visible where the person clicked (a row says Copied, a switch turns on), that is the feedback. If it isn’t, because a menu closed or the result happens somewhere else, a toast says what happened.
A toast
One call to toast.success(). The Toaster in the app layout shows it for 3 seconds.
- A toast is past tense, under 10 words, with no period: “Invite sent”.
- An error people must act on stays next to the problem. It is never a toast that fades.
- Bridge has no undo. Anything that removes or stops something asks first with
ConfirmDialog.
See Feedback and undo and Destructive actions.
Theming
Light and dark come from one set of tokens. data-theme="dark" on <html> switches every token at once; data-theme="light" on an element keeps that area light inside a dark page. Bridge follows the member’s choice, not the device setting.
- Bridge OS stores the choice in
localStorageasbridge-themeand sets it before the page draws, so there is no flash of the wrong theme. - A screen built from tokens needs no
dark:classes. Usedark:only for something a token can’t say. - Every example on this site has a Light and Dark switch. Check both before you ship.
Accessibility
Base UI gives the package’s components their roles, focus handling and keyboard behavior. Naming things and choosing the right element stay with you.
- Every control has a name. An icon-only button has an
aria-labelthat says the action. - Focus always shows: a 2px ring with a 2px gap. Never remove it.
- Nothing clickable is smaller than 28px.
- Everything a pointer can do, the keyboard can do. Every dialog has a title.
- Status is never shown by color alone; a word always goes with it.
- Motion respects reduced motion.
The full checklist is in Accessibility.
Finding things
Every component, pattern and template has a page. Press / anywhere on this site to search the sidebar by title and description.
| Looking for | Go to |
|---|---|
| A component for a job | All components |
| How to solve a problem that comes up again and again | Patterns, starting with Loading, empty and error |
| The shape of a whole page | Picking a template |
| What an app’s frame, widths and habits are | All apps |
| A token’s value | Design tokens |
| Everything as plain text, for agents | For agents |
Working together
DESIGN.md at the root of the design-system repo is the visual spec. The handbook explains it and shows it running; no app reinterprets it. When code and DESIGN.md disagree, DESIGN.md wins.
- Each app gets its character from layout, density and behavior, not from new colors, fonts or corner sizes.
- If the system can’t do something a screen needs, change the system first (Component lifecycle), not the one screen.
Where new code goes
| What you’re writing | Where it goes |
|---|---|
| A screen or a route | The app. In Bridge OS: src/app/(shell)/<area>/, with its parts in _components/ next to it. |
| Loading data and its helpers | The app, next to the area that uses it. In Bridge OS: src/lib/<area>/. |
| A control or layout part used in a second place | packages/ui, with a handbook page (Component lifecycle → Share at two). |
| A hook or helper that knows nothing about the product | packages/ui/src/hooks or packages/ui/src/lib. |
| A different look of an existing component | A new option on that component, not a copy in the app. |
New to the code itself? See Repository and Component lifecycle.
Rules
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.