BuildBaseBuildBase

Theming

Re-skin the SDK's settings dialog, pickers and forms with CSS variables, including dark mode and scoped overrides.

Every color and corner radius in the pre-built UI resolves from a small set of CSS custom properties. Override them and the settings dialog, plan picker, credit store and banners all follow - no forked components and no !important.

Load the stylesheet

Import the SDK stylesheet once, at the top of your app's entry file (see Installation):

import '@buildbase/sdk/css';

The stylesheet is scoped. Its rules and its variables apply inside elements with the saas-os-ui class, so it does not restyle the rest of your app. SaaSOSProvider renders a saas-os-ui container that the dialogs portal into, and BetaForm wraps itself in one.

Warning

The variables are declared on .saas-os-ui, not on :root. An override on :root is shadowed by the SDK's own declaration and changes nothing. Target .saas-os-ui instead.

Override the variables

Put your overrides in a stylesheet that loads after @buildbase/sdk/css. Same selector, later rule wins:

.saas-os-ui {
  --primary: 262 83% 58%;
  --primary-foreground: 0 0% 100%;
  --ring: 262 83% 58%;
  --radius: 0.75rem;
}

Values are HSL triplets without the hsl() wrapper, shadcn/ui style. The SDK wraps them itself and derives tints from them - status banners use hsl(var(--success) / 0.1) for their fill - so a hex value or a full hsl(...) call breaks those colors.

Variable reference

These are the variables the shipped stylesheet declares, with its light and dark defaults:

VariableUsed forLightDark
--backgroundDialog and page surface0 0% 100%222.2 84% 4.9%
--foregroundMain text222.2 84% 4.9%210 40% 98%
--cardCard surfaces0 0% 100%222.2 84% 4.9%
--card-foregroundText on cards222.2 84% 4.9%210 40% 98%
--popoverDropdowns and popovers0 0% 100%222.2 84% 4.9%
--popover-foregroundText in dropdowns and popovers222.2 84% 4.9%210 40% 98%
--primaryButtons, active sidebar item, selected states222.2 47.4% 11.2%210 40% 98%
--primary-foregroundText on primary210 40% 98%222.2 47.4% 11.2%
--secondarySecondary buttons210 40% 96%217.2 32.6% 17.5%
--secondary-foregroundText on secondary222.2 84% 4.9%210 40% 98%
--mutedSubtle fills, loading bars, table headers210 40% 96%217.2 32.6% 17.5%
--muted-foregroundSecondary text, hints, labels215.4 16.3% 46.9%215 20.2% 65.1%
--accentHover highlights210 40% 96%217.2 32.6% 17.5%
--accent-foregroundText on hover highlights222.2 84% 4.9%210 40% 98%
--destructiveErrors, delete actions, overage0 84.2% 60.2%0 62.8% 30.6%
--destructive-foregroundText on destructive210 40% 98%210 40% 98%
--successSuccess banners, paid and active badges142 72% 29%142 69% 58%
--success-foregroundText on success0 0% 100%222.2 47.4% 11.2%
--warningWarnings, expiring credits, near-limit states26 90% 37%38 92% 50%
--warning-foregroundText on warning0 0% 100%222.2 47.4% 11.2%
--infoInfo notices, selected plans, usage bars224 76% 48%217 91% 60%
--info-foregroundText on info0 0% 100%222.2 47.4% 11.2%
--borderBorders and dividers214.3 31.8% 91.4%217.2 32.6% 17.5%
--inputInput outlines214.3 31.8% 91.4%217.2 32.6% 17.5%
--ringFocus rings222.2 84% 4.9%212.7 26.8% 83.9%
--radiusCorner radius; sm and md derive from it0.5reminherits light

--radius sets the large radius directly. The medium radius is --radius minus 2px and the small one is --radius minus 4px, so one value rounds everything consistently.

Dark mode

Since SDK 0.0.71 the stylesheet declares its dark values under .dark .saas-os-ui, .saas-os-ui.dark and .saas-os-ui .dark. A dark class on your <html> element (next-themes, Tailwind's darkMode: 'class') is an ancestor of the SDK container, so it switches SDK UI to dark with no CSS of your own. A dark class on the SDK container, or on an element inside it, works too.

To bring your brand into dark mode, override only the variables you change:

.dark .saas-os-ui {
  --primary: 262 83% 68%;
  --primary-foreground: 222.2 47.4% 11.2%;
}

This selector has the same specificity as the SDK's own dark rule, so the one loaded last wins: load it after the SDK stylesheet. Anything you leave out keeps the shipped dark value.

The stylesheet does not follow the operating system setting by itself. To do that, declare the full dark column inside a media query, and load it after the SDK stylesheet:

@media (prefers-color-scheme: dark) {
  .saas-os-ui {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;
    --primary: 262 83% 68%;
    --primary-foreground: 222.2 47.4% 11.2%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 40% 98%;
    --success: 142 69% 58%;
    --success-foreground: 222.2 47.4% 11.2%;
    --warning: 38 92% 50%;
    --warning-foreground: 222.2 47.4% 11.2%;
    --info: 217 91% 60%;
    --info-foreground: 222.2 47.4% 11.2%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --ring: 212.7 26.8% 83.9%;
  }
}

Tip

The block above is the shipped dark column with a brand --primary. Keep every surface and text variable in it: anything you leave out falls back to the SDK's light default, which reads as a white card on a dark page.

Scope overrides

Because the variables live on the SDK container, the usual CSS cascade decides which override applies:

  • App-wide brand - one .saas-os-ui { ... } rule, as above.
  • One region only - wrap SDK UI you render inline (for example <BetaForm />) in your own class and target it: .marketing .saas-os-ui { --primary: 24 95% 53%; }.
  • Typography - the stylesheet sets a system font stack on .saas-os-ui. Override it there: .saas-os-ui { font-family: var(--font-inter), system-ui, sans-serif; }.

Dialogs render in the provider's portal container at the end of SaaSOSProvider, not where you call openWorkspaceSettings(), so a region-scoped rule does not reach them. Theme dialogs with the app-wide rule.

FullScreenLoader and the error boundary fallback use inline styles so they render before the stylesheet loads. They still read --background, --muted-foreground and --destructive, with the light defaults as fallbacks.

Visibility and wording

Colors are CSS. Which parts of the UI show, and what they say, is the ui prop on SaaSOSProvider (see the Installation props table). Two options matter most when you rebrand:

import { SaaSOSProvider } from '@buildbase/sdk/react';

<SaaSOSProvider
  // ...serverUrl, version, orgId
  ui={{
    messages: {
      settings: { sidebar: { credits: 'Tokens', subscription: 'Billing' } },
      credits: { buyCredits: 'Buy tokens' },
    },
    errorBoundary: { title: 'Something went wrong at Acme', retryLabel: 'Retry' },
  }}
>
  • messages deep-merges over the active locale, so your terms apply in every language the SDK ships. Keys you leave out keep the SDK wording.
  • errorBoundary takes plain strings rather than translation keys, because the boundary has to render even when the translation layer has crashed.

The same prop hides sections and actions (ui.settings.sections, ui.workspaceSwitcher). It can only hide: a visible flag never bypasses a missing permission.

Next steps

  • Overview - the settings screens and page components these variables style.
  • Internationalization - the locales messages overrides sit on top of.