This page is about the CSS you write next to the components. Design tokens is the architecture underneath it. This is the part you apply while building a screen. If you do not have a theme block yet, generate one in the theme builder before styling the page around it.
Five hard rules
- Never hardcode a colour in the CSS around the components. Use the
--bb-*properties and your surfaces follow the theme and dark mode. - Never override component internals, the
.bb-*__*element classes, from your own code. Restyle through tokens, variants or documented props. - One primary action per view region. Everything else is
secondary,outlineorghost. - Keep
destructivefor irreversible actions, and send a destructive flow throughuseConfirmfirst. - Respect the radius. Use
var(--bb-radius)for any container of your own that sits next to a component. Do not invent corner radii.
Rule 2 costs the most when you break it, because nothing tells you. Treat everything inside a component as private API.
Some components are built to be extended visually, and each one names its
extension point on its own page. BbTree has you draw
connector lines anchored to its --indent and --gap.
BbTabs exposes --list-h. Read the component's page
before you write any CSS that mentions a .bb- class.
Three lanes
In order of preference. Most work never leaves the first two.
Component look, use props. Pick a variant, a size, compact. Never write
CSS to make a component look different.
Page layout and spacing, use utilities on your own wrapper elements:
flex items-end gap-3, grid gap-6, max-w-3xl. Components size themselves.
You only place them.
Genuinely custom visuals, use --bb-* tokens. The few rules left over are
things like a panel of your own or a status dot. Build them from theme tokens and
they follow theming and dark mode with nothing extra written.
The variant vocabulary
Variant names are typed per family, so an unregistered name is a compile error rather than an unstyled element.
| Family | Built-in variants |
|---|---|
BbButton / BbDropdownButton | primary secondary outline ghost destructive link, plus none |
BbBadge | primary secondary outline ghost destructive |
BbAlert | primary outline destructive warning |
BbToast / useToast | default success info warning destructive |
BbConfirm, dropdown items, tooltips | default destructive |
What the shared names mean:
| Variant | Use for |
|---|---|
primary | The single main action of a view region |
secondary | Supporting actions |
outline | Neutral actions on busy surfaces |
ghost | Low-emphasis, inline or icon-only actions |
destructive | Irreversible actions, confirm first |
link | Navigation disguised as text; buttons only |
none | Unstyled escape hatch. Avoid |
The sets differ per family on purpose. BbToast carries success and info
because transient feedback is where they belong. BbButton and BbBadge do not,
which is what the last section is about.
Using colour
Reach for colour more than the monochrome default suggests, and make it mean something. A calm, mostly neutral surface with a few deliberate spots of colour reads as confident. Colour sprinkled for decoration reads as noise.
Add colour for one of three reasons, never for prettiness:
- Semantic status. A build passed, a payment failed, a quota is low.
- State. Selected, active, dirty, pending, just changed. Colour is a fast, pre-verbal "this updated".
- Telling apart a small, closed set. A few statuses or priorities the eye should separate at a glance. Keep the set small and fixed; a per-row rainbow is noise, not signal.
Then keep it soft:
- Leave the chrome neutral. Hover, pressed and focus use
--bb-hover,--bb-pressedand--bb-ring, and selection uses the primary tint. Put semantic colour on the content: a status dot, a soft badge, a tinted alert. That is what keeps colour from feeling jarring. - Prefer soft tints to saturated fills on anything bigger than a dot. The
library's own recipe is
color-mix(in oklab, <hue> 10–15%, var(--bb-panel))for the fill, with the hue itself as the text. Mixing toward--bb-panelkeeps it theme- and dark-mode-aware. Small dots may be saturated. - One primary action per region still holds. Semantic colour is not a licence for five loud buttons.
What already carries status
Reach for these before you build anything.
| Need | Reach for |
|---|---|
| A status marker next to a label or row | BbIndicator — default, success, info, warning, destructive |
| A persistent message | BbAlert — primary, outline, warning, destructive |
| Transient feedback | BbToast and useToast — the full status set |
| A removable or error tag | BbBadge destructive; neutral tags use secondary or outline |
| A field that needs attention but is not invalid | any form control's warnings and hasWarnings props |
The warning input state has no equivalent elsewhere, so read that last row twice.
Every form control takes warnings, the messages, and hasWarnings, which forces
the chrome. They mirror errors and hasErrors exactly, and while the messages
show they are referenced from the control's aria-describedby. Set both and the
errors win: the warnings never appear.
Registering your own
BbButton and BbBadge
ship no success, info or warning variants, and there is no
--bb-success or --bb-info token. --bb-warn and --bb-text-warn colour the
warning input state and nothing else. They are not a variant vocabulary.
A first-class semantic set was considered and rejected. shadcn carries no such vocabulary, and neither does this library.
"Locked" means typed, not closed. The registries are open by design, so each
app owns its own tones. Register the name through the Vite or Nuxt plugin, then
style .bb-button--<name> in a project stylesheet kept apart from the library
defaults. The registries are buttonVariants, badgeVariants, alertVariants,
confirmVariants, dropdownItemVariants, tooltipVariants and
toastVariants. The state recipe is in
Design tokens.
The soft treatment to follow, one accent per status:
.bb-badge--success {
--accent: oklch(0.65 0.15 150);
background: color-mix(in oklab, var(--accent) 12%, var(--bb-panel));
color: var(--accent);
border-color: color-mix(in oklab, var(--accent) 25%, transparent);
}
The percentages come from the emphasis ladder, so a variant you register behaves
like one the library shipped. 12% sits between the resting 8% and hover 15%
stops, and 25% is the tinted-border stop. Mixing toward --bb-panel rather
than a hex is what carries it through the dark theme without a second rule.