Skip to content

Theming

Every component reads its colors, radii and type from tokens. The Salesforce themes fill them with SLDS 2's own values, extracted from Salesforce's stylesheet; Neutral fills them from shadcn's variables. Pick a theme with its theme step, npx shadcn@latest add @wisty/<theme> (see Installation), and dark mode with the dark class on <html>.

The themes

Theme Add it with What it is
Cosmos @wisty/cosmos Salesforce's current look, and the default
Lightning Blue @wisty/lightning-blue Salesforce's earlier Lightning look, SLDS 1. It has no dark mode.
Neutral @wisty/neutral shadcn's own look

Cosmos and Neutral have a dark mode, under the dark class. A theme step writes one theme into your stylesheet's :root and .dark, so the whole app takes it. Edit the values in place to adjust them; the next theme step writes over them.

More than one theme

To switch themes while the app runs, or to show one theme inside another, import the theme files by hand (see Installation) and pick a theme with an attribute:

Theme Set it with
Cosmos data-theme="cosmos", or nothing
Cosmos dark data-theme="cosmos" and the dark class
Lightning Blue data-theme="lightning-blue"
Neutral data-theme="neutral"
Neutral dark data-theme="neutral" and the dark class

Import tokens.css, then each theme file you use, Neutral's last. Put the attribute and the dark class on the same element, usually <html>. Popovers, selects and dialogs render into document.body, so a theme on a smaller element doesn't reach them.

<html data-theme="cosmos" class="dark">
	<body>
		…
	</body>
</html>

Neutral

Neutral is for apps that use the components and blocks without Salesforce's look. It starts from shadcn's neutral theme and reads shadcn's variables: --primary, --background, --border, --ring, --radius and the rest. After npx shadcn@latest add @wisty/neutral, they're in your stylesheet's :root and .dark with shadcn's values. Change them as you would in any shadcn project. Components and blocks follow, and so do tokens shadcn has no variable for, like the selected item's fill and the link color.

:root {
	--primary: oklch(0.55 0.2 260);
	--radius: 0.5rem;
}
.dark {
	--primary: oklch(0.7 0.15 260);
}

With the files imported by hand, Neutral needs data-theme="neutral" on <html>. Its light-mode values have zero specificity and its dark-mode values have .dark's, so your own :root and .dark rules after the import win.

Success, warning and info colors, which shadcn has no variable for, come from Tailwind's palette. Display density works in Neutral. Salesforce's brand hooks don't apply there, so set --primary instead.

Your own overrides

Override the tokens, never the class strings: components read var(--primary), var(--border) and the rest, the names shadcn uses where SLDS has the same idea. After a theme step, the theme's values are your :root and .dark values: edit them there, and run the step again to get the theme's own back. With the files imported by hand, the default theme sits at zero specificity, so your own :root values win without !important.

Fonts work the way they do in shadcn, in every theme. Set --font-sans and --font-mono on :root. The page's text, the font-sans and font-mono utilities and every component use them. Without them, each theme keeps its own stack: Salesforce's system font in Cosmos and Lightning Blue, Tailwind's in Neutral. font-serif is Tailwind's own utility and reads your --font-serif the same way.

:root {
	--font-sans: "Geist", system-ui, sans-serif;
	--font-mono: "Geist Mono", ui-monospace, monospace;
}

Your org's brand

In the Salesforce themes, an admin can give a Salesforce org its own theme: a brand color, optional accent colors and an optional text color. Lightning puts them on the page as Salesforce's own styling hooks. The components read the same hooks and use the theme's colors until you set them.

Set them on <html>. With the theme files, any element above the one with data-theme works too. Hooks set lower down are ignored.

:root {
	/* The brand ramp Lightning generates from the org's brand color */
	--slds-r-color-brand-50: #c935a4;
	--slds-r-color-brand-40: #a02982;
	--slds-r-color-brand-15: #430c35;
	/* …and the other steps, 5 to 95 */

	/* Optional, when the org's theme overrides them */
	--slds-g-color-accent-container-1: light-dark(#0b7a3e, #c935a4);
	--slds-g-color-on-surface-3: light-dark(#430c35, #faddf0);
}
Hooks What they color
--slds-r-color-brand-5 to --slds-r-color-brand-95 The brand ramp: buttons, links, focus, selection
--slds-g-color-accent-1 to -3, accent-container-1 to -3, on-accent-1 to -3 The org's accent overrides, and text on them
--slds-g-color-border-accent-1 Borders in the accent color
--slds-g-color-on-surface-3 Default text and headings

To copy an org's values, open any Lightning page in that org and read these properties on <html> in the browser's developer tools.

An app that runs inside Salesforce in its own frame, such as a Multi-Framework app or a Canvas app, doesn't receive the org's hooks or its light or dark mode. Salesforce doesn't pass them into the frame, so the component that embeds the app has to forward them. Matching a Salesforce org lists what to read and set.

Display density

Each Salesforce user picks a display density: comfy, the default, or compact. Compact puts field labels beside their fields and tightens spacing. Set data-density="compact" on <html>, the element that holds the theme:

<html data-density="compact"></html>
What changes in compact How
Cards, page headers Less padding
Card, section and accordion titles One step smaller
Body text Line height normal instead of 1.5
Record detail, inline edit, form Labels beside values and controls, from a 48rem screen; above them on a narrower one

Buttons, inputs, menus and tables keep their size, as in Lightning. The values are Salesforce's own: the themes keep SLDS's density hooks (--slds-g-spacing-var-*, --slds-g-font-scale-var-*, --slds-g-font-lineheight-var-base), so a page that sets them directly gets the same result.

A field with layout="auto", the record blocks' default, follows the nearest data-density. Put data-density="comfy" on a region to keep its fields stacked on a compact page, or give one block layout="stacked" or layout="horizontal".

Matching a Salesforce org

What the embedding component reads from the Lightning page, and what the app sets:

The org's Read on the Lightning page Set in the app
Design system SLDS 2 when --slds-g-color-surface-1 on <html> holds a light-dark() pair; otherwise SLDS 1 data-theme="cosmos" for SLDS 2, data-theme="lightning-blue" for SLDS 1, with both theme files imported
Brand The hooks in the table under Your org's brand The same hooks on <html>
Light or dark slds-color-scheme_dark on <body> The dark class
Density Compact when --slds-g-spacing-var-1 differs from --slds-g-spacing-1 data-density="compact"

Lightning Blue and Salesforce's other built-in themes are SLDS 1. Cosmos and custom themes are SLDS 2. An SLDS 1 theme's color arrives in the accent hooks, so forwarding every hook under Your org's brand carries it too.

Icons

Salesforce's icons come from the @salesforce-ux/icons package, unaltered. Copy its dist/salesforce-lightning-design-system-icons folder to public/assets/icons, or point an IconProvider at wherever you serve them.