@sorb/leaf reference

When you finish this page you know every export of the Sorb™ React SDK, which group it belongs to, and the smallest working example for each. You need a React 18 app (or any page that can run an ES module) and a generated token set. For the step-by-step setup, read React SDK first — this page is the reference.

npm install @sorb/leaf

@sorb/leaf has one runtime dependency, @sorb/core, and one peer dependency, react@^18. React is only needed for the components and hooks; sorbInit, the sanitizer, the legacy map and the target adapters all run without it.

The eight groups

The package exports 30 names. They fall into eight groups, and most apps only ever touch the first.

GroupExportsYou need it when
Provider, hooks, bannerSorbProvider, useTokens, useToken, useIsPreview, usePreviewState, PreviewBannerYou have a React app. This is the whole setup.
Framework-freesorbInitYou are not on React, or you drive Sorb from a plain script.
Dark modeuseTheme, ThemeToggle, buildModeStylesheet, injectModeStylesheet, clearModeStylesheet, MODE_STYLESHEET_ID, tailwindDarkMode, dataThemeDarkModeYour token set ships light and dark values.
Target adaptersreactBootstrapTarget, mantineTarget, tailwindV4Target, shadcnTarget, primevueTarget, muiTarget, angularMaterialTargetYou want to see which UI kit a build targets, or register your own.
Legacy mapapplyLegacyMap, clearLegacyMap, computeLegacyOverride, indexLegacyMap, normalizeProp, normalizeValueYour app has hardcoded literals you have not tokenized yet.
SecuritysanitizeCssValueYou inject token values yourself instead of through the provider.
VerificationverifyResolvedYou want to assert the running DOM matches the committed resolved map.
Diagnostics(config only — diagnostics.allowedOrigins)You are debugging which project an app is bound to.

Provider, hooks, and banner

SorbProvider is the only thing most apps mount. It takes one required prop, config, applies the committed token values as CSS custom properties on document.documentElement, and swaps them for a proposed set while a preview is active. The generated variables.css still has to be imported — the provider overrides those variables, it does not create them.

// main.jsx — lifted from the reference app, sorb-demo/main.jsx
import React from "react";
import { createRoot } from "react-dom/client";
import { SorbProvider, PreviewBanner } from "@sorb/leaf";
import { tokens } from "./src/tokens/generated/tokens";
import "./src/tokens/generated/variables.css";
import { App } from "./src/App";

const sorbConfig = {
  namespace: "my-app",
  tokens,
  preview: {
    enabled: import.meta.env.MODE !== "production",
    origin: "http://localhost:7777",
    pollInterval: 1500,
    expectPrefixes: ["bs-"],
  },
};

createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <SorbProvider config={sorbConfig}>
      <App />
      <PreviewBanner />
    </SorbProvider>
  </React.StrictMode>,
);

Rendering SorbProvider without config throws Cannot read properties of undefined (reading 'tokens') at mount. The full SorbConfig shape — darkTokens, resolved, orgKey, cloudBase, diagnostics, legacyMap — is in the types table below.

PreviewBanner is safe to render unconditionally. It renders nothing when there is no preview, and otherwise shows one of three states: blue while a preview is live, amber when the preview loaded but matched none of your expectPrefixes, red when a requested ?preview= could not be loaded. The red state renders even though isPreview is false, because a failed preview falls back to committed tokens. Each state's cause and fix is on Troubleshooting.

Read the active values with the hooks:

import { useToken, useTokens, useIsPreview, usePreviewState } from "@sorb/leaf";

function Swatch() {
  const primary = useToken("color-primary"); // → '#3B5BDB'
  const all = useTokens(); // the whole active TokenSet
  const isPreview = useIsPreview();
  const { previewId, previewMismatch, previewError, clearPreview } =
    usePreviewState();

  return <div style={{ background: primary }}>{isPreview ? previewId : "committed"}</div>;
}

All five hooks must be called inside a mounted SorbProvider.

Framework-free: sorbInit

sorbInit(config) is the same runtime without React — connection resolution, committed and preview loading, mode-aware injection, polling or SSE. It returns a small store, so you can drive it from a plain module script, a Vue or Svelte app, or a legacy page.

<script type="module">
  import { sorbInit } from "@sorb/leaf";
  import { tokens } from "./tokens/generated/tokens.js";

  const sorb = sorbInit({
    namespace: "my-app",
    tokens,
    preview: { enabled: true, origin: "http://localhost:7777" },
  });

  sorb.subscribe(() => {
    const { isPreview, previewId } = sorb.getState();
    document.title = isPreview ? `preview ${previewId}` : "my app";
  });
</script>

The instance exposes getState, subscribe, setMode and clearPreview — see SorbInstance in the types table. SorbProvider is a thin React shell over exactly this object, so the DOM behavior is identical either way.

Dark mode

Dark mode activates when your config carries a darkTokens set alongside tokens. The provider then injects a sorb-tokens stylesheet holding both value sets instead of writing flat inline custom properties, and the mode hooks start having a visible effect. Without darkTokens, setMode still works but has nothing to switch.

import { SorbProvider, ThemeToggle, useTheme } from "@sorb/leaf";
import { tokens } from "./tokens/generated/tokens";
import { tokens as darkTokens } from "./tokens/generated/tokens.dark";

const config = { namespace: "my-app", tokens, darkTokens };

function Header() {
  const { mode, setMode, resolvedScheme } = useTheme();
  // mode: 'auto' | 'light' | 'dark' — the manual selection
  // resolvedScheme: 'light' | 'dark' — what is actually on screen right now
  return <ThemeToggle />;
}

The convention used to express dark mode defaults to the react-bootstrap adapter's data-bs-theme attribute. Override it with darkModeConvention when you target a different kit: tailwindDarkMode is Tailwind's .dark class, and dataThemeDarkMode is the generic [data-theme] attribute.

import { dataThemeDarkMode } from "@sorb/leaf";

const config = { namespace: "my-app", tokens, darkTokens, darkModeConvention: dataThemeDarkMode };

buildModeStylesheet, injectModeStylesheet, clearModeStylesheet and MODE_STYLESHEET_ID are the pieces the provider uses internally. They are exported so a build step can produce or assert the exact same CSS; you do not need them to use dark mode.

Target adapters

Importing @sorb/leaf registers seven TargetAdapter records into the @sorb/core connector registry as a side effect. Each one names the Style Dictionary format a build should emit for that UI kit, the custom-property prefixes the vocabulary guard expects, and the kit's dark-mode convention.

ExportConnector idEmitsDark mode
reactBootstrapTargetreact-bootstrapsorb/tokenset-esmdata-bs-theme
mantineTargetmantinesorb/mantine-varsMantine color scheme
tailwindV4Targettailwind-v4sorb/tailwind-theme.dark class
shadcnTargetshadcnsorb/shadcn-theme.dark class
primevueTargetprimevuesorb/primevue-presetPrimeVue preset
muiTargetmuisorb/mui-varsMUI color scheme
angularMaterialTargetangular-materialsorb/mat-sys-vars--mat-sys-*

You rarely import these by name. Read one when you need its prefixes:

import { mantineTarget } from "@sorb/leaf";
import { getTarget } from "@sorb/core";

console.log(mantineTarget.expectPrefixes); // feed into config.preview.expectPrefixes
console.log(getTarget("tailwind-v4")); // same record, from the registry

The registry itself — registerTarget, getTarget (plus the registerSource/getSource and registerCodeSource/getCodeSource pairs for the other two connector axes), resolveConnectorIds — lives in @sorb/core.

Legacy map

An app that still has hardcoded colors can be re-skinned before it is tokenized. sorb-seed adapt writes .sorb/adapt-report.json; its auto rows are a legacy map. Pass them to the provider and every element whose computed value equals a row's raw gets an inline var(--<cssVar>, <raw>) override — additive, and restored on unmount.

import report from "./.sorb/adapt-report.json";

const legacyMap = report.rows.filter((r) => r.status === "auto");

<SorbProvider config={sorbConfig} legacyMap={legacyMap}>
  <App />
</SorbProvider>;

Drive it yourself outside React with applyLegacyMap(root, legacyMap) and clearLegacyMap(handle). computeLegacyOverride, indexLegacyMap, normalizeProp and normalizeValue are the pure decision helpers underneath — useful when you build your own remapping pass or test one.

Security

Every token value the provider writes goes through sanitizeCssValue first. It is deny-by-default: it rejects control characters, the context-break characters {, } and ;, @import, javascript: and &lt;/, and any CSS function outside its allowlist — which is what stops url(, image-set( and expression(.

import { sanitizeCssValue } from "@sorb/leaf";

sanitizeCssValue("#f26722"); // { ok: true, value: '#f26722' }
sanitizeCssValue("url(https://evil.example/x.png)"); // { ok: false, reason: ... }

Call it yourself only if you inject values through your own code path. Two further guards are config, not exports: preview.allowedOrigins is the allowlist of non-localhost bridge origins the SDK will accept a preview from, and preview.expectPrefixes declares the custom-property vocabulary your app actually reads, so a preview that would change nothing is flagged instead of failing silently.

Verification

verifyResolved reads each token's value back off :root and asks the bridge whether the running app matches the committed resolved map.

import { verifyResolved } from "@sorb/leaf";

const result = await verifyResolved(["button-primary-bg-default"], {
  origin: "http://localhost:7777",
});
// { ok: true, checked: 1, matched: 1 }

Call it from inside a mounted provider. Without one, custom properties read back as unresolved var(...) references and the result is { ok: false, reason: 'provider-not-applied' } rather than a misleading mismatch. Omit key for the local bridge; pass config.preview.key for a hosted one.

Diagnostics

The SDK answers a sorb-ping postMessage with a sorb-hello fingerprint — namespace, the last four characters of the key, the SDK version, the bridge origin, and the outcome of the last preview attempt. It never posts unsolicited, it replies only to the exact origin that pinged, and it never sends a full key.

Only allowlisted origins get an answer. The Sorb Cloud dashboard is allowlisted by default; extend the list for a self-hosted dashboard:

const config = {
  namespace: "my-app",
  tokens,
  diagnostics: { allowedOrigins: ["https://dashboard.example.com"] },
};

Nothing about authorization, entitlement or routing may be derived from a sorb-hello. It exists so you can tell which project an app is bound to when a preview does not appear.

Exports

ExportKindDescriptionSource
SorbProviderfunctionSorbProvider — the React shell over sorbInit (./core.js, the framework-free injector; component-compat-roadmap P0).src/TokenProvider.jsx:44
sorbInitfunctionFramework-free Sorb entry point.src/core.js:132
PreviewBannerfunctionDrop-in banner that appears at the bottom of the screen for a Sorb preview.src/PreviewBanner.jsx:23
useTokensfunctionReturns the full active token set (committed or preview).src/hooks.js:7
useTokenfunctionReturns a single token value by key.src/hooks.js:19
useIsPreviewfunctionReturns whether a preview token set is currently active.src/hooks.js:33
usePreviewStatefunctionReturns full preview state — useful for building a preview banner.src/hooks.js:53
useThemefunctionReal-dark-mode (spec D3): the manual mode selection + the live-resolved scheme actually in effect.src/hooks.js:71
ThemeTogglefunctionDrop-in Light / Dark / Auto mode toggle (real-dark-mode spec D3).src/ThemeToggle.jsx:26
sanitizeCssValuefunctionValidate an untrusted CSS token value before it is injected via setProperty.src/sanitize.js:69
verifyResolvedfunctionRead each token's resolved value off :root and ask the bridge whether the running app matches the committed resolved map.src/verify.js:37
buildModeStylesheetfunctionBuilds the mode-aware CSS text carrying both a light and (optionally) a dark value-set for the same token ids — real-dark-mode spec D2/D3.src/modeStylesheet.js:42
injectModeStylesheetfunctionUpserts a &lt;style id="sorb-tokens"&gt; tag in &lt;head&gt; carrying mode-aware CSS (real-dark-mode spec D3) — the injection path used when a theme has both a light and a dark value-set (see buildModeStylesheet, ./modeStylesheet.js).src/apply.js:89
clearModeStylesheetfunctionRemoves the &lt;style id="sorb-tokens"&gt; tag injected by injectModeStylesheet, if present.src/apply.js:106
MODE_STYLESHEET_IDvalueThe id of the &lt;style&gt; tag injectModeStylesheet upserts.src/apply.js:64
tailwindDarkModevalueTailwind's darkMode: 'class' convention — a .dark class toggled on documentElement (typically &lt;html&gt;).src/darkModeConventions.js:26
dataThemeDarkModevalueA generic [data-theme="..."] attribute convention — the same shape as react-bootstrap's data-bs-theme but under the more common data-theme attribute name, for hosts that don't use Bootstrap's specific convention.src/darkModeConventions.js:39
applyLegacyMapfunctionWalk root (and its descendants), and for every element whose computed value for a mapped property equals a raw in the legacyMap, override that element's INLINE style for that property to var(--&lt;cssVar&gt;, &lt;raw&gt;).src/legacyDom.js:25
clearLegacyMapfunctionRestore every inline style override recorded by applyLegacyMap, returning each element to its original (usually empty) inline value.src/legacyDom.js:77
computeLegacyOverridefunctionPURE decision logic.src/legacyMap.js:138
indexLegacyMapfunctionIndex a legacyMap into a prop → [&#123; normValue, cssVar, raw &#125;] lookup so the decision function is O(1)-per-prop instead of scanning the whole array.src/legacyMap.js:108
normalizePropfunctionMap a CSS property name (camelCase from JS style objects, or kebab-case from computed style) to a canonical kebab-case form for comparison.src/legacyMap.js:23
normalizeValuefunctionNormalize a style value so an authored literal ("#0F65EF", "4", "4px") compares equal to its computed-style form.src/legacyMap.js:80
reactBootstrapTargetvalueTargetAdapter react-bootstrap (the default target): emits sorb/tokenset-esm, expects the bs- prefix, dark mode via data-bs-theme.src/targets/reactBootstrap.js:31
mantineTargetvalueTargetAdapter mantine: emits sorb/mantine-vars and declares the kit prefixes the vocabulary guard expects.src/targets/mantine.js:28
tailwindV4TargetvalueTargetAdapter tailwind-v4: emits sorb/tailwind-theme; dark mode via Tailwind's .dark class.src/targets/tailwindV4.js:25
shadcnTargetvalueTargetAdapter shadcn: emits sorb/shadcn-theme for shadcn/ui's CSS-variable theme.src/targets/shadcn.js:27
primevueTargetvalueTargetAdapter primevue: emits sorb/primevue-preset, the first JS-emitting target format.src/targets/primevue.js:35
muiTargetvalueTargetAdapter mui: emits sorb/mui-vars for MUI v6's CSS-variables mode.src/targets/mui.js:29
angularMaterialTargetvalueTargetAdapter angular-material: emits sorb/mat-sys-vars for Angular Material 20's M3 system variables.src/targets/angularMaterial.js:55

SorbProvider

const SorbProvider = ({ config, legacyMap, children })

SorbProvider — the React shell over sorbInit (./core.js, the framework-free injector; component-compat-roadmap P0). ALL runtime logic (connection resolution, committed/preview loading, mode-aware injection, SSE/poll, dark-mode state) now lives in sorbInit; this component's only job is to bridge that instance's pub-sub store into React state and expose the same TokenContext shape as before — non-breaking, byte- identical behavior to the pre-extraction implementation. sorbInit is created in the mount useEffect (not during render) so timing — and StrictMode double-invoke safety — matches the original implementation, which did all its DOM work in a mount-only effect too. The optional legacyMap (Legacy-React adapter, roadmap §6) is an ADDITIVE, non-destructive DOM overlay layered on top of the shell — it never touches sorbInit. When present, after tokens apply it remaps any element whose hardcoded literal matches a row's raw to var(--&lt;cssVar&gt;, &lt;raw&gt;), and restores the originals on unmount.

ParameterTypeDescription
props{ config: import('./types').SorbConfig, legacyMap?: import('./types').LegacyMapRow[], children: React.ReactNode, }

sorbInit

function sorbInit(config)

Framework-free Sorb entry point. Resolves the connection, loads committed/preview tokens onto document.documentElement, and returns a small store (getState/subscribe) plus setMode/clearPreview. No React, no JSX — safe to call from a plain &lt;script type="module"&gt;. Byte-identical DOM behavior to SorbProvider: same guard/vocab/mode-aware injection logic, just driven by a manual pub-sub store instead of React state.

ParameterTypeDescription
configimport('./types').SorbConfig

Returns SorbInstance.

PreviewBanner

const PreviewBanner = ()

Drop-in banner that appears at the bottom of the screen for a Sorb preview. Renders in three states (see previewBannerModel): - blue "active" — a healthy live preview, - amber "mismatch" — preview active but likely re-skins nothing (B4), - red "error" — a deliberately-requested ?preview= couldn't be loaded (it may belong to a different project). The error state renders even though isPreview is false, since a failed preview falls back to committed tokens (spec jj-demo-rebind-and-diagnosis D2 — kill the silent-404). Safe to include unconditionally — renders nothing when there's no preview and no preview error.

// In your app root, after <SorbProvider>
<PreviewBanner />

useTokens

const useTokens = ()

Returns the full active token set (committed or preview).

Returns import('./types').TokenSet.

useToken

const useToken = (key)

Returns a single token value by key.

ParameterTypeDescription
keystring

Returns string.

const primary = useToken('color-primary') // → '#3B5BDB'

useIsPreview

const useIsPreview = ()

Returns whether a preview token set is currently active. Useful for showing a preview indicator in your app.

Returns boolean.

usePreviewState

const usePreviewState = ()

Returns full preview state — useful for building a preview banner. previewMismatch is true when a preview loaded but its tokens don't match the app's preview.expectPrefixes (vocabulary mismatch — see B4); use it to render a warning state. Always false unless the guard is opted into. previewError is &#123; id, outcome &#125; (outcome: 'not_found'|'unauthorized'|'network') when a deliberately-requested ?preview= fetch failed and the SDK fell back to committed tokens — the case that used to be totally silent. null otherwise. A not_found typically means the preview id belongs to a different project than this app's key is bound to.

const { isPreview, previewId, previewMismatch, previewError, clearPreview } = usePreviewState()

useTheme

const useTheme = ()

Real-dark-mode (spec D3): the manual mode selection + the live-resolved scheme actually in effect. mode is meaningful for every app; setMode('light'|'dark') always works. It only visibly changes anything once the consumer's SorbConfig carries a darkTokens set (otherwise there's no dark stylesheet for the attribute toggle to select).

Returns { mode: 'auto'\|'light'\|'dark', setMode: (mode: 'auto'\|'light'\|'dark') => void, resolvedScheme: 'light'\|'dark': }

const { mode, setMode, resolvedScheme } = useTheme()

ThemeToggle

const ThemeToggle = ({ className } = {})

Drop-in Light / Dark / Auto mode toggle (real-dark-mode spec D3). Purely a thin useTheme() view — three buttons that call setMode, with the active one highlighted. Renders unconditionally (safe even in a single-mode app, where setMode still works but has nothing to visibly toggle since there's no injected dark stylesheet). Unstyled beyond minimal inline layout — bring your own CSS/className to match your app, same philosophy as PreviewBanner.

ParameterTypeDescription
[props]{ className?: string }
// In your app root, alongside <PreviewBanner>
<ThemeToggle />

sanitizeCssValue

const sanitizeCssValue = (value)

Validate an untrusted CSS token value before it is injected via setProperty. Pure — does not touch the DOM. Rules (deny-by-default): - non-string / empty input is rejected. - reject ASCII control chars \x00-\x1f. - reject the context-break chars &#123; &#125; ;. - reject (case-insensitive, whitespace-tolerant) @import, javascript:, and the markup-break &lt;/. - extract every identifier( and reject if ANY is not in the allowlist (this is what stops url(, image-set(, expression(, paint(, …).

ParameterTypeDescription
valueunknown

Returns { ok: boolean, value: string, reason?: string: }

verifyResolved

const verifyResolved = async (tokens, { origin = 'http://localhost:7777', key, fetch: fetchImpl } = {})

Read each token's resolved value off :root and ask the bridge whether the running app matches the committed resolved map. Precondition: call from inside a mounted &lt;SorbProvider&gt; — it applies the resolved token literals onto :root. Without it, custom props read back as var(...) refs (outputReferences css) and the result is &#123; ok:false, reason:'provider-not-applied' &#125; rather than a misleading mismatch.

ParameterTypeDescription
tokensstring[]Token names or --cssVars to check (e.g. 'button-primary-bg-default').
[opts]{ origin?: string, key?: string, fetch?: typeof globalThis.fetch }key is the hosted-bridge bearer key (config.preview.key). Omit for the no-auth localhost bridge — no Authorization header is then sent.

Returns Promise<{ok:boolean, reason?:string, checked?:number, matched?:number, mismatches?:Array<{cssVar:string,expected:any,got:any: >, unknown?:string[], error?:string}>}

buildModeStylesheet

const buildModeStylesheet = (lightVars, darkVars, darkMode)

Builds the mode-aware CSS text carrying both a light and (optionally) a dark value-set for the same token ids — real-dark-mode spec D2/D3. Pure — no DOM. Returns a CSS string meant to be upserted into a &lt;style id="sorb-tokens"&gt; tag by injectModeStylesheet (apply.js). Contract (must stay byte-shape-stable — the demo/cloud emit agents match this exact shape): ```css :root { --a: 1; color-scheme: light; }

ParameterTypeDescription
lightVarsimport('./types').TokenSetLight-mode token map. Keys may be bare ('primary') or already ---prefixed ('--primary') — normalized here.
darkVarsimport('./types').TokenSet | null | undefinedDark-mode token map, same key shape. null/undefined/&#123;&#125; ⇒ single-mode.
darkModeimport('@sorb/core').DarkModeConvention | null | undefinedThe active TargetAdapter's dark-mode convention (e.g. reactBootstrapTarget.darkMode). Undefined ⇒ single-mode.

Returns string: CSS text, ready to inject verbatim.

injectModeStylesheet

const injectModeStylesheet = (css)

Upserts a &lt;style id="sorb-tokens"&gt; tag in &lt;head&gt; carrying mode-aware CSS (real-dark-mode spec D3) — the injection path used when a theme has both a light and a dark value-set (see buildModeStylesheet, ./modeStylesheet.js). DELIBERATELY SEPARATE from applyTokens/clearTokenOverrides (inline style.setProperty, above): those two stay untouched and are still what TokenProvider calls for a light-only theme, so a single-mode app's output is byte-identical to today (back-compat gate, spec §3 D3). This function is only reached when a theme actually has a dark mode — a &lt;style&gt; tag is required (not inline styles) because only a stylesheet can carry a @media (prefers-color-scheme: dark) block and attribute-selector rules; inline styles on documentElement can express neither. The css argument is expected to already be sanitized (buildModeStylesheet runs every value through sanitizeCssValue before it reaches here) — this function does no further validation, it only manages the tag's lifecycle.

ParameterTypeDescription
cssstringCSS text, e.g. from buildModeStylesheet(...).

Returns void.

clearModeStylesheet

const clearModeStylesheet = ()

Removes the &lt;style id="sorb-tokens"&gt; tag injected by injectModeStylesheet, if present. Counterpart to clearTokenOverrides for the mode-aware (dual-mode) path.

Returns void.

tailwindDarkMode

const tailwindDarkMode

Tailwind's darkMode: 'class' convention — a .dark class toggled on documentElement (typically &lt;html&gt;). Tailwind has no canonical "light" class (light is just the absence of .dark), so lightSelector is omitted: a manual "light" choice cannot out-rank an OS dark preference under this convention (see modeAction.js's resolveModeAction) — a known limitation of class-only theming without a light marker.

applyLegacyMap

const applyLegacyMap = (root, legacyMap)

Walk root (and its descendants), and for every element whose computed value for a mapped property equals a raw in the legacyMap, override that element's INLINE style for that property to var(--&lt;cssVar&gt;, &lt;raw&gt;). Returns a handle that clearLegacyMap uses to restore the original inline values. Non-destructive: only inline element.style[prop] is touched, and the prior inline value (often empty) is captured so it can be restored exactly.

ParameterTypeDescription
[root=document.body]Element|Document|nullsubtree to remap
legacyMapLegacyMapRow[]the report's auto rows

Returns LegacyMapHandle.

clearLegacyMap

const clearLegacyMap = (handle)

Restore every inline style override recorded by applyLegacyMap, returning each element to its original (usually empty) inline value.

ParameterTypeDescription
handleLegacyMapHandle|null

Returns void.

computeLegacyOverride

const computeLegacyOverride = (prop, computedValue, legacyMap)

PURE decision logic. Given a property, the element's computed value for that property, and the legacyMap (array or pre-built index), return the override string var(--&lt;cssVar&gt;, &lt;raw&gt;) when the value matches a mapped raw, else null. This is the unit-tested core of the shim.

ParameterTypeDescription
propstringCSS property (camelCase or kebab-case)
computedValuestring|numberthe element's computed value for prop
legacyMapLegacyMapRow[]|Map<string, any[]>rows, or an index from indexLegacyMap

Returns string\|null.

indexLegacyMap

const indexLegacyMap = (legacyMap)

Index a legacyMap into a prop → [&#123; normValue, cssVar, raw &#125;] lookup so the decision function is O(1)-per-prop instead of scanning the whole array.

ParameterTypeDescription
legacyMapLegacyMapRow[]

Returns Map<string, Array<{ normValue: string, cssVar: string, raw: string: >>}

normalizeProp

const normalizeProp = (prop)

Map a CSS property name (camelCase from JS style objects, or kebab-case from computed style) to a canonical kebab-case form for comparison.

ParameterTypeDescription
propstring

Returns string.

normalizeValue

const normalizeValue = (value)

Normalize a style value so an authored literal ("#0F65EF", "4", "4px") compares equal to its computed-style form. Trims, lowercases, collapses whitespace, canonicalizes colors to rgb()/rgba() (so hex raw matches the computed rgb()), and treats a bare unitless number as its px form (covers borderRadius: 4"4px").

ParameterTypeDescription
valuestring|number

Returns string.

Types

JSDoc typedefs. Import them in your own JSDoc with import('@sorb/leaf').Name.

TokenValue

type TokenValue = string | number

Source: src/types.js:4

TokenSet

A flat map of token name → value.

type TokenSet = Object.<string, TokenValue>

Source: src/types.js:8

PreviewConfig

PropertyTypeDescription
enabledbooleanWhether to allow preview mode at all. Set to false in production builds. e.g. enabled: process.env.NODE_ENV !== 'production'
origin (optional)stringWhere the local Sorb CLI is running. Defaults to http://localhost:7777. Only localhost/127.0.0.1/[::1] origins (any port) are trusted by default; any other origin must be listed in allowedOrigins or preview is blocked.
allowedOrigins (optional)string[]Extra exact origins (e.g. a staging or hosted bridge) to trust in addition to localhost. Never enable preview against an untrusted origin in production.
key (optional)stringBearer key for a hosted bridge (Sorb Cloud). When set, preview/verify requests send Authorization: Bearer &lt;key&gt;; when unset (localhost sorb dev) no header is sent. Use a read-only publishable sorb_pk_… key in anything distributable — supply it via env/config at deploy time, never hardcoded in source.
pollInterval (optional)numberHow often to poll for token updates while a preview is active, in milliseconds. Defaults to 1500.
expectPrefixes (optional)string[]Vocabulary/contract guard (B4). Token-key prefixes this app actually consumes (e.g. ['bs-']). When set, a loaded preview that applies tokens but matches NONE of these prefixes is flagged (previewMismatch context state) and a console.warn is emitted — catching the silent-no-op where the banner lights but nothing re-skins. Omit/empty ⇒ guard disabled (default).

Source: src/types.js:13

ResolvedToken

A resolved token with full metadata, as produced by sorb-seed. The optional deprecated / replacedBy fields are only present when the DTCG source carries $deprecated: true / $extensions.sorb.replacedBy.

PropertyTypeDescription
idstring
cssVarstring
value*
tierstring
typestring
deprecated (optional)true
replacedBy (optional)string

Source: src/types.js:42

ResolvedConnection

The effective connection sorb-cloud resolved for an org/publishable key (E1 — hosted-bridge-modes, config-migration.md). See src/connection.js for the assumed GET &lt;cloudBase&gt;/api/orgs/resolve?key= contract — TODO, reconcile against the real sorb-cloud endpoint when it lands.

PropertyTypeDescription
bridgeMode'A'|'B'|'C'|stringThe org's configured bridge mode.
bridgeUrlstringThe bridge origin to preview against.
orgIdstring|nullNeeded to build the SSE subscribe URL.
tokenSourcestring|null
previewPersistenceboolean|null
transport'sse'|'poll'Which preview-update transport to use.

Source: src/types.js:56

LegacyMapRow

A single row of the legacy-map shim — a subset of the engine's auto row from .sorb/adapt-report.json (roadmap §6). The full engine row also carries file/loc/tokenId/confidence/candidates/status; the runtime shim only consumes &#123; raw, prop, cssVar &#125;, so any auto row is a valid LegacyMapRow.

PropertyTypeDescription
rawstringThe original hardcoded value as authored, e.g. "#0F65EF" or "4px". Doubles as the var() fallback so removing the provider restores it exactly.
propstringThe CSS property the value applies to, e.g. "background" or "borderRadius" (camelCase or kebab-case both accepted).
cssVarstringThe target token's custom-property name WITHOUT the leading --, e.g. "button-primary-bg-default".

Source: src/types.js:70

LegacyMapHandle

Opaque handle returned by applyLegacyMap, passed to clearLegacyMap to restore the original inline styles. Internal shape may change.

PropertyTypeDescription
restoresArray<{ el: HTMLElement, prop: string, prev: string }>

Source: src/types.js:88

SorbConfig

PropertyTypeDescription
namespacestringYour app or design system namespace.
tokensTokenSetCommitted token set — bundled at build time. Always used in production. Used as fallback if preview fails.
darkTokens (optional)TokenSetCommitted DARK-mode token set (real-dark-mode spec D3) — same token ids as tokens, dark values. When present, SorbProvider injects a mode-aware &lt;style id="sorb-tokens"&gt; stylesheet (buildModeStylesheet) instead of the flat inline applyTokens path, and setMode/useTheme become meaningful. Omit for a single-mode (light-only) app — unchanged, byte-identical behavior to today.
darkModeConvention (optional)import('@sorb/core').DarkModeConventionOverride the dark-mode convention used to build the mode-aware stylesheet. Defaults to the react-bootstrap TargetAdapter's darkMode (data-bs-theme) — override only for a non-default target.
resolved (optional)ResolvedToken[]Full resolved token array from sorb-seed output. When provided, SorbProvider will emit a dev-mode console.warn for any token flagged as deprecated.
preview (optional)PreviewConfigPreview configuration. Omit or set enabled: false to disable entirely. An explicit preview.origin always wins over org-key resolution (below) — this is today's file-mode / Mode C path and is never overridden.
orgKey (optional)stringOrg/publishable key (E1). Like an analytics SDK key: when set (and no explicit preview.origin is pinned), SorbProvider resolves bridge mode/url, token source, and preview persistence from sorb-cloud instead of requiring a local sorb.config.json. Purely additive — omit for today's file-mode behavior, unchanged.
publishableKey (optional)stringAlias for orgKey — either field name works; orgKey is checked first when both are set (see getOrgKey in connection.js).
cloudBase (optional)stringOverride the sorb-cloud base URL used for org-key resolution. Defaults to connection.js's DEFAULT_CLOUD_BASE. Mainly for tests/staging.
diagnostics (optional){ allowedOrigins?: string[] }Diagnosis channel (spec jj-demo-rebind-and-diagnosis D2). The leaf answers a &#123; type:'sorb-ping' &#125; postMessage with a sorb-hello fingerprint (namespace + key last4 + version + bridge origin + preview outcome) — but ONLY when the ping's event.origin is allowlisted. Baked defaults are Sorb Cloud's dashboard (https://app.sorbcloud.com + staging); set diagnostics.allowedOrigins to extend the allowlist for a self-hosted dashboard. The leaf never posts unsolicited and replies only to the exact pinging origin — see src/diagnostics.js.
legacyMap (optional)LegacyMapRow[]Legacy-React adapter shim: the auto rows from .sorb/adapt-report.json. When present, after committed tokens are applied the provider remaps any element whose hardcoded computed style matches a row's raw to var(--&lt;cssVar&gt;, &lt;raw&gt;) — non-destructive, reversible on unmount.

Source: src/types.js:95

TokenContextValue

PropertyTypeDescription
tokensTokenSetCurrently active token set (committed or preview).
isPreviewbooleanTrue when a preview token set is loaded.
previewIdstring | nullThe active preview ID, or null.
previewMismatchbooleanTrue when the active preview applied tokens but none matched the app's preview.expectPrefixes (vocabulary mismatch — the app likely won't re-skin). Always false when the guard is not opted into.
previewError{ id: string, outcome: 'not_found'|'unauthorized'|'network' }|nullSet when a deliberately-requested ?preview= fetch failed and the SDK silently fell back to committed tokens (the previously-invisible failure — spec jj-demo-rebind-and-diagnosis D2). outcome classifies the HTTP/network cause: not_found (404 — cross-tenant id or expired preview), unauthorized (401/403), network (unreachable/parse). null on the normal path.
clearPreview() => voidClears the preview, removes the query param, loads committed tokens.
mode'auto'|'light'|'dark'The current MANUAL mode selection (real-dark-mode spec D3). 'auto' (default) defers to the OS prefers-color-scheme via the injected media query — no data-bs-theme attribute is set. 'light'/'dark' are a manual override that always wins (sets data-bs-theme).
setMode(mode: 'auto'|'light'|'dark') => voidChange the manual mode selection.
resolvedScheme'light'|'dark'The scheme actually in effect right now: mode itself when it's 'light'/'dark', otherwise the live-tracked OS prefers-color-scheme result while mode === 'auto'.

Source: src/types.js:147

SorbState

type SorbState = { tokens: import('./types').TokenSet, isPreview: boolean, previewId: string|null, previewMismatch: boolean, previewError: { id: string, outcome: 'not_found'|'unauthorized'|'network' }|null, mode: 'auto'|'light'|'dark', resolvedScheme: 'light'|'dark', }

Source: src/core.js:99

SorbInstance

type SorbInstance = { getState: () => SorbState, subscribe: (listener: (state: SorbState) => void) => (() => void), setMode: (next: 'auto'|'light'|'dark') => void, clearPreview: () => void, destroy: () => void, }

Source: src/core.js:99

PreviewBannerModel

PropertyTypeDescription
visiblebooleanRender the banner at all?
variant (optional)'active'|'mismatch'|'error'
id (optional)string|nullThe preview id to show in the chip.
title (optional)stringBold headline.
message (optional)stringSecondary explanatory line.
buttonLabel (optional)stringAction-button label.
background (optional)stringBanner background (token-bindable var()).
accent (optional)stringTop-border accent colour.

Source: src/previewBannerModel.js:11

Next

Works with Figma. Not affiliated with, or endorsed by, Figma. Figma is a trademark of Figma, Inc.