Designer tutorial: setting up multi-brand exports
You’re a designer working with the Obra shadcn/ui Pro kit, and you need a single Figma file to drive theming for multiple brands or product lines. This tutorial walks through how to structure your variable modes so the CSS Export plugin gives you a clean per-brand globals.css for each one.
How the plugin actually works#
Before setting anything up, it helps to know what the plugin does:
- It scans your file for specific collections by name —
shadcn colors,chart colors,typography,border radii, plus the Obra-extended set (shadows,spacing). - For each detected collection, the Setup tab lets you pick one mode to export at a time.
- For color collections specifically, the plugin auto-detects a paired
-darkmode. If you selectbrand-a, it’ll automatically emit thebrand-a-darkmode as a.dark { … }block. - By default, the output is written as
:root { … }for the selected mode, plus.dark { … }for the paired dark mode if one is found. The plugin does not auto-emit.brand-a { … }class selectors — brand scoping is handled outside the plugin. - Beyond the default CSS, the right-side panel also has a Design Tokens 1.0 tab (DTCG 2025.10 format, with
$type/$value) and a Style Dictionary tab (same tree, with baretype/valuekeys), so you can pipe the same selected mode(s) into iOS, Android, or other build systems. Dark-mode handling differs between CSS and tokens: CSS scopes dark values inside a separate.dark { … }block, while the token exports keep light and dark in a single flat tree — each dark value is emitted as a sibling of its light counterpart with a-darksuffix (e.g.,color.backgroundandcolor.background-dark), and your build pipeline decides how to apply them.
That means the multi-brand workflow is a manual export per brand: pick a brand’s mode in the dropdown, run the export, save the output as one file. Switch to the next brand’s mode, run again, save as another file. Wrap each one in the right scope on the dev side.
Recommended setup: multiple brand modes inside the existing collections#
The clean way to do this is to keep the existing collections (shadcn colors, chart colors, etc.) and add a paired light/dark mode per brand inside each one. Don’t duplicate collections — the plugin only exports one mode per collection per run, so duplicating doesn’t get you anything you couldn’t do with modes.
A typical setup inside shadcn colors:
| Mode name | Use |
|---|---|
shadcn | Default (kit defaults, light) |
shadcn-dark | Default dark, paired with shadcn |
brand-a | Brand A, light |
brand-a-dark | Brand A, dark — paired with brand-a |
brand-b | Brand B, light |
brand-b-dark | Brand B, dark — paired with brand-b |
Mirror the same pattern in chart colors if your chart palettes differ per brand. Collections that don’t change per brand (typography, border radii, shadows, spacing) can keep their default modes — you don’t need brand-specific variants there unless they actually differ.
Tip: stick to the
<name>/<name>-darknaming convention. That’s what the plugin uses to auto-pair light and dark — name a modebrand-a-darkand selectingbrand-awill automatically emit the paired dark block.
Step 1: Plan which collections need brand modes#
Add brand modes only to the collections that actually vary per brand:
- Always:
shadcn colors— primary, accent, neutrals, and the rest of the theme tokens almost always change per brand. - Often:
chart colors— if charts are used in product UI and brands have distinct chart palettes. - Sometimes:
typography,border radii— only if the brands have different fonts or different visual sharpness/softness. - Rarely:
shadows,spacing— usually shared across brands. Only add brand modes if a brand’s spacing or shadows really do differ.
Fewer modes means less upkeep. Only fork what actually changes.
Step 2: Add the brand modes#
Inside each collection that needs to vary:
- Add a mode for each brand’s light variant (e.g.,
brand-a). - Add a paired
-darkmode for each one (e.g.,brand-a-dark). - Repoint each token in the brand modes to the upstream values you want — e.g., point
primaryat a differentshadcn/*scale, or point thetheme-neutrals/*row at a differenttw-raw/*neutral scale.
The supporting collections (neutrals, shadcn theme colors, raw tailwind colors) stay shared across brands. You’re just choosing which row of those tables each brand’s shadcn colors row references.
Step 3: Wire layers to the kit’s existing collections#
Don’t change the collection names. Components in the kit reference shadcn colors, chart colors, etc. by their existing names — the brand modes inside those collections work automatically because mode-switching happens at the variable level, not the collection level.
To preview a brand in Figma, use Apply variable mode on a frame and pick the brand mode (brand-a, brand-b, etc.). Components recolor automatically. The Obra Batch Mode Switcher plugin makes flipping every frame on a page much faster.
Step 4: Export one brand at a time#
The plugin’s two panels do exactly what you need for this workflow: the left side (Setup tab) is where you choose what to export, the right side (Code tab) is where you copy the result. So the loop per brand is:
- On the left side, in each color collection’s row (
shadcn colors,chart colors), open the mode dropdown and pick the brand’s light mode — e.g.,brand-a. The plugin auto-pairs the matchingbrand-a-darkmode and emits a.dark { … }block from it. - On the right side, switch to the Code tab. The CSS regenerates as soon as your selections change.
- Copy the output.
- Save it as a per-brand stylesheet — for example,
brand-a.css. - Repeat for the next brand: change the mode dropdowns on the left to
brand-b, copy from the right, save asbrand-b.css. And so on for every brand.
The plugin doesn’t have a “export all modes” button — each brand is its own export, run by hand.
A heads-up on custom variables#
Most established design teams have added their own variables on top of the kit — extra brand-specific tokens (e.g., marketing-hero-gradient, legacy-link-blue), domain-specific scales, or custom collections that aren’t part of the kit’s default set. The plugin won’t pick those up.
The plugin recognizes collections by name — only collections matching shadcn colors, chart colors, typography, border radii, shadows, spacing, and the typography-utility set are read. A custom collection called, say, marketing tokens is invisible to the plugin no matter how it’s structured.
Even within the recognized collections, the plugin only emits the specific token names shadcn/ui expects (background, foreground, primary, the rest of the 27 semantic tokens, the 5 chart slots, and so on) — adding an arbitrary token like marketing-hero-gradient to shadcn colors won’t get it into the output, because the plugin doesn’t generically scan and emit every token it finds. It works against a fixed shadcn-aligned shape.
The right answer here is to customize the plugin to your needs. Org and Enterprise plans include the plugin’s source code as a downloadable build — your dev team can take our plugin as a starting point and adapt it to recognize your custom collection names, emit your own token exceptions, and produce an export shape that fits your design system instead of fighting the default one. See the manual install procedure for how to install and run a customized build.
Step 5: Hand the per-brand CSS files to dev#
Tell your dev counterpart:
- Each file (
brand-a.css,brand-b.css, …) contains a complete:root { … }and.dark { … }block for one brand. - They wrap or scope each file however the app expects — common patterns:
- Per-brand class selectors. Wrap each file in
.brand-a { … }/.brand-a.dark { … }and load all of them. Toggle a class on<html>to pick the active brand. - Per-route stylesheets. Load
brand-a.cssfor/brand-a/*routes andbrand-b.cssfor/brand-b/*. The CSS works at:rootbecause only one brand’s stylesheet is loaded at a time. - Build-step namespacing. Run a small script that prefixes the variable names per brand and merges the files. Heavier but more flexible.
- Per-brand class selectors. Wrap each file in
Whatever they pick, the contract is the same: you ship one CSS file per brand, they ship one wrapper/scope per brand.
Common pitfalls#
- Inconsistent token coverage. Every brand mode in
shadcn colorsshould define the same set of tokens. Ifbrand-bis missingaccent foreground, the export falls back to whatever the default is — usually wrong. - Forgetting the
-darkmode. Without a-darkpaired mode, the plugin emits no.dark { … }block for that brand and dark-mode users see fallbacks. - Renaming brand modes after launch. Once devs have shipped wrappers like
.brand-a, renaming the mode in Figma silently breaks the next export. - Painting layers with raw colors. A layer painted with a
tw-raw/blue/500value won’t pick up the brand. The chain only works when every layer referencesshadcn colors.
When this isn’t the right fit#
If your brands are very close (same neutrals, same typography, only the primary color changes), modes-per-brand is overkill. Use the shadcn/ui Create plugin to spin up a brand-tinted variant of the kit on demand instead.
If your brands diverge so much that they really need different files entirely, that’s also fine — keep one Figma file per brand and run the plugin per file. Don’t try to force everything into a single multi-mode setup if it’s fighting you.