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:
| Variable | Used for | Light | Dark |
|---|---|---|---|
--background | Dialog and page surface | 0 0% 100% | 222.2 84% 4.9% |
--foreground | Main text | 222.2 84% 4.9% | 210 40% 98% |
--card | Card surfaces | 0 0% 100% | 222.2 84% 4.9% |
--card-foreground | Text on cards | 222.2 84% 4.9% | 210 40% 98% |
--popover | Dropdowns and popovers | 0 0% 100% | 222.2 84% 4.9% |
--popover-foreground | Text in dropdowns and popovers | 222.2 84% 4.9% | 210 40% 98% |
--primary | Buttons, active sidebar item, selected states | 222.2 47.4% 11.2% | 210 40% 98% |
--primary-foreground | Text on primary | 210 40% 98% | 222.2 47.4% 11.2% |
--secondary | Secondary buttons | 210 40% 96% | 217.2 32.6% 17.5% |
--secondary-foreground | Text on secondary | 222.2 84% 4.9% | 210 40% 98% |
--muted | Subtle fills, loading bars, table headers | 210 40% 96% | 217.2 32.6% 17.5% |
--muted-foreground | Secondary text, hints, labels | 215.4 16.3% 46.9% | 215 20.2% 65.1% |
--accent | Hover highlights | 210 40% 96% | 217.2 32.6% 17.5% |
--accent-foreground | Text on hover highlights | 222.2 84% 4.9% | 210 40% 98% |
--destructive | Errors, delete actions, overage | 0 84.2% 60.2% | 0 62.8% 30.6% |
--destructive-foreground | Text on destructive | 210 40% 98% | 210 40% 98% |
--success | Success banners, paid and active badges | 142 72% 29% | 142 69% 58% |
--success-foreground | Text on success | 0 0% 100% | 222.2 47.4% 11.2% |
--warning | Warnings, expiring credits, near-limit states | 26 90% 37% | 38 92% 50% |
--warning-foreground | Text on warning | 0 0% 100% | 222.2 47.4% 11.2% |
--info | Info notices, selected plans, usage bars | 224 76% 48% | 217 91% 60% |
--info-foreground | Text on info | 0 0% 100% | 222.2 47.4% 11.2% |
--border | Borders and dividers | 214.3 31.8% 91.4% | 217.2 32.6% 17.5% |
--input | Input outlines | 214.3 31.8% 91.4% | 217.2 32.6% 17.5% |
--ring | Focus rings | 222.2 84% 4.9% | 212.7 26.8% 83.9% |
--radius | Corner radius; sm and md derive from it | 0.5rem | inherits 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' },
}}
>messagesdeep-merges over the active locale, so your terms apply in every language the SDK ships. Keys you leave out keep the SDK wording.errorBoundarytakes 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
messagesoverrides sit on top of.