Skip to content

Bridge components ship with optional default styles. Import them for a ready-to-use appearance, or skip the import entirely for headless usage where you control all styling yourself.

Add this import to your root app/layout.tsx:

import '@nebulr-group/bridge-nextjs/styles';

The stylesheet provides two layers:

  • Structural CSS: layout, spacing, and sizing that components need to render correctly.
  • Visual defaults: a minimal but complete out-of-the-box appearance so forms look reasonable with no extra work. These include a visible input border/focus ring, an indigo primary button, and colored error/success alert banners.

The --bridge-* variables below are the supported way to restyle every Bridge page and component, kept stable across releases. Set them once in your app’s stylesheet:

/* src/app.css */
:root {
  --bridge-primary: #0f766e;
  --bridge-primary-fg: #ffffff;
  --bridge-border: #e2e8f0;
  --bridge-border-radius: 10px;
}

The plugin declares its defaults on :where(:root), which has zero specificity, so your :root wins whatever order the two stylesheets load in. Set a token on a wrapper instead of :root to theme one area (<div style="--bridge-primary: #7c3aed">). Tokens are rung 1 of the customisation rungs in How Bridge works: rung 0 is your layout around every Bridge page, then the frame / heading snippets (2), taking over one page (3) and headless (4).

TokenDefaultStyles
--bridge-primary#4f46e5Primary buttons, the selected tab and plan interval, the active workspace
--bridge-primary-hoverprimary, 15% darkerPrimary button hover
--bridge-primary-fg#ffffffText on primary surfaces
--bridge-primary-lightprimary at 10%Tint behind the active workspace
--bridge-input-focusprimaryInput focus ring
--bridge-bg#ffffffDialogs, menus, panels
--bridge-foreground#111827Text on those surfaces, workspace names
--bridge-muted#6b7280Secondary text, hints, table headings
--bridge-muted-bg#f3f4f6Subtle fills: tab tracks, hovers, the MFA backup code
--bridge-border#d1d5dbInput, table, card and secondary button borders
--bridge-border-radius6pxCorners of inputs, buttons, alerts, cards and dialogs
--bridge-overlayrgba(15, 23, 42, 0.45)Backdrop behind dialogs
--bridge-alert-error-bg / -fg / -border#fef2f2 / #991b1b / #fca5a5Errors, critical billing notices, cancelled plan badge
--bridge-alert-success-bg / -fg / -border#f0fdf4 / #166534 / #86efacConfirmations, active plan badge
--bridge-alert-info-bg / -fg / -border#eff6ff / #1e40af / #bfdbfeInfo notices, trial badge
--bridge-alert-warning-bg / -fg / -border#fffbeb / #92400e / #fcd34dWarnings, a quota nearing its limit, past-due badge

The three derived tokens (-hover, -light, --bridge-input-focus) follow --bridge-primary wherever you set it, unless you set them too.

TokenDefaultStyles
--bridge-auth-page-padding3rem 1remThe default container of the sign-in pages (<BridgeAuthRoutes>)
--bridge-billing-page-width60remThe subscription pages (<BridgeBillingRoutes>, <BridgePaywallPage>)
--bridge-billing-page-padding3rem 1remThe subscription pages
--bridge-paywall-bgrgba(15, 23, 42, 0.72)Backdrop of the <BridgePaywall> overlay and the billing lockscreen
--bridge-paywall-panel-bg--bridge-bgPanel of the <BridgePaywall> overlay

Fonts and body text colour. Bridge pages render inside your app/layout.tsx, so they inherit your font, text colour and background like any other page.

Two tokens had two names. The old ones keep working, and will be removed in a later major version:

DeprecatedUse
--bridge-primary-foreground--bridge-primary-fg
--bridge-bg-muted--bridge-muted-bg

Keep the styles import and point the tokens at your Tailwind theme, so Bridge follows it. Tailwind v4 exposes the theme as CSS variables:

/* src/app.css */
@import 'tailwindcss';

@theme {
  --color-brand-600: #0f766e;
  --color-brand-700: #115e59;
}

:root {
  --bridge-primary: var(--color-brand-600);
  --bridge-primary-hover: var(--color-brand-700);
  --bridge-primary-fg: var(--color-white);
  --bridge-foreground: var(--color-slate-900);
  --bridge-muted: var(--color-slate-500);
  --bridge-muted-bg: var(--color-slate-100);
  --bridge-border: var(--color-slate-200);
  --bridge-border-radius: var(--radius-lg);
}

If a Tailwind variable resolves to nothing, your Tailwind version emits only the theme variables it sees used; declare the block with @theme static to keep them all.

On Tailwind v3, the same with theme():

:root {
  --bridge-primary: theme('colors.teal.700');
  --bridge-primary-hover: theme('colors.teal.800');
  --bridge-border: theme('colors.slate.200');
  --bridge-border-radius: theme('borderRadius.lg');
}

Utility classes still work on anything that takes a class prop, and on your own markup inside the frame snippet.

All visual-default rules, and the token defaults on :where(:root), use the :where() pseudo-class, which has zero specificity. Any class or element selector in your own CSS wins automatically, no !important needed.

For example, the default primary button is styled as:

:where(.bridge-btn-primary) {
  background-color: var(--bridge-primary);
  /* ... */
}

Your own .bridge-btn-primary { background: red; } or even a simple .my-button { background: red; } will override it without specificity battles.

All components forward className and style props to their root element, so you can target them directly:

<LoginForm className="my-login-form" />
.my-login-form input {
  border-radius: 0;   /* square inputs */
}

You can also use the style prop for inline overrides:

<LoginForm style={{ '--bridge-primary': '#7c3aed' } as React.CSSProperties} />

Bridge components expose data attributes that you can use as CSS selectors for state-based styling:

AttributeValuesDescription
[data-active="true"]"true"Active tab
[data-loading="true"]"true"Loading state
[data-state="active"]"active"Active status
[data-state="disabled"]"disabled"Disabled status
[data-variant="error"]"error"Error variant (alerts)
[data-variant="info"]"info"Info variant (alerts)
[data-variant="success"]"success"Success variant (alerts)
[data-variant="danger"]"danger"Danger variant (alerts)
[data-bridge-plan-selector](none)Plan selector root
[data-bridge-plan-card](none)Individual plan card
[data-current="true"]"true" / "false"Current plan card
[data-bridge-alert](none)Alert component
[data-bridge-auth-form](none)Auth form wrapper
[data-bridge-passkey-login](none)Passkey login button
[data-bridge-sso-button](none)SSO button
[data-bridge-spinner](none)Loading spinner
[data-bridge-api-tokens](none)API token management root
[data-bridge-workspace-selector](none)Workspace selector root
[data-bridge-workspace-item](none)Individual workspace item

Example: style the active workspace differently.

[data-bridge-workspace-item][data-active="true"] {
  background: var(--my-highlight);
  border-color: var(--my-accent);
}

If you would rather style every component from scratch, skip the import entirely:

  // app/layout.tsx
  import { BridgeProvider } from '@nebulr-group/bridge-nextjs/client';
- import '@nebulr-group/bridge-nextjs/styles';

Components render as plain, unstyled HTML. Use the className prop and the data attributes listed above to apply your own styles. The structural layout (flexbox, grid) is embedded in the stylesheet, so without it you have full control over how components are laid out.