@sorb/core reference
When you finish this page you know the shapes every other Sorb™ package
agrees on — tiers, the resolved token entry, the semantic-role contract, and
the connector registry — and where each one is enforced in code. You need no
setup to read this page; you only install @sorb/core directly if you are
writing a connector or citing its typedefs from your own JSDoc.
npm install @sorb/core
In a normal app you never install this yourself — @sorb/seed, @sorb/juice
and @sorb/leaf all depend on it already, and importing any of them pulls it
in transitively.
Tiers
Sorb's token taxonomy has three tiers. TIERS lists them most-specific first;
TIER_RANK gives each a number so the lowest rank wins when two tokens
resolve to the same CSS custom property — a component-tier token always
overrides a semantic or primitive one bound to the same variable.
import { TIERS, TIER_RANK } from "@sorb/core";
TIERS; // ['component', 'semantic', 'primitive']
TIER_RANK; // { component: 0, semantic: 1, primitive: 2 }
The capture annotator (@sorb/seed) and the Sorb plugin both rank candidate
bindings against TIER_RANK — it is the single place that precedence is
decided.
The resolved token shape
ResolvedToken is the one shape that must not drift between packages: the
bindable, fully-resolved entry that Style Dictionary's sorb/resolved-map
format emits, that the bridge serves at GET /tokens/resolved, and that the
capture annotator and the plugin both consume.
/**
* @typedef {Object} ResolvedToken
* @property {string} id Dotted token id, e.g. "button.primary.bg.default"
* @property {string} cssVar Emitted CSS custom property, e.g. "--button-primary-bg-default"
* @property {string|number} value Fully resolved value (no var() chains)
* @property {Tier} tier Taxonomy tier — drives precedence via TIER_RANK
* @property {TokenType} type DTCG $type
*/
Reference it from your own JSDoc without re-declaring it:
/** @param {import('@sorb/core').ResolvedToken[]} resolved */
ResolvedMap is just ResolvedToken[] — the shape of .sorb/resolved.json
and the bridge's GET /tokens/resolved response body. The full set —
including the capture-side shapes LayerNode, SorbAnnotation and
StoryIndex — is in the types table below.
Semantic-role contract and the role-id semver rule
The framework target adapters (sorb/mantine-vars, sorb/mui-vars,
sorb/mat-sys-vars, sorb/shadcn-theme, sorb/primevue-preset) don't emit
against one specific token kit's names — they emit against a canonical set of
role ids, and a kit maps its own token ids onto those roles.
import { DEFAULT_ROLE_IDS, ALL_ROLE_IDS, resolveRole } from "@sorb/core";
DEFAULT_ROLE_IDS.color; // ['color.surface', 'color.ink', 'color.brand', ...]
ALL_ROLE_IDS; // flat list across color / radius / shadow / typography
// A format resolves a role id through your kit's roleMap — identity when
// your kit already uses the canonical ids as its own token ids:
resolveRole("color.brand", options.roleMap); // -> your kit's token id
If your kit uses different names, pass options.roleMap (role id → your
token id) to the format instead of forking it — see the per-format options
shapes on the @sorb/seed reference.
This is a semver-load-bearing list. Adding a role id to
DEFAULT_ROLE_IDS/ALL_ROLE_IDS is a minor bump. Renaming or removing
one is a major bump for @sorb/core and @sorb/seed, because every
format consumer resolves against this exact set.
Connector registry
Tokens move through Sorb along three pluggable axes, each with its own
registry inside connectors:
| Axis | Registry | Register | Look up | Default id |
|---|---|---|---|---|
| Source — where tokens + geometry come in | connectors.source | registerSource | getSource | DEFAULT_SOURCE_ID ('storybook-dom') |
| Code source — where the running app lives | connectors.codeSource | registerCodeSource | getCodeSource | DEFAULT_CODE_SOURCE_ID ('local') |
| Target — how tokens bind into the app | connectors.target | registerTarget | getTarget | DEFAULT_TARGET_ID ('react-bootstrap') |
@sorb/seed registers the default source connector, @sorb/juice the
default code-source connector, and @sorb/leaf registers all seven target
adapters as an import side effect.
import { registerTarget, getTarget, resolveConnectorIds } from "@sorb/core";
registerTarget({
id: "my-target",
emitFormat: "SORB_TOKENSET",
expectPrefixes: ["my-"],
});
getTarget("my-target"); // -> the adapter you just registered
getTarget("does-not-exist"); // throws: Unknown target adapter: "does-not-exist"
resolveConnectorIds({});
// -> { source: 'storybook-dom', codeSource: 'local', target: 'react-bootstrap' }
registerTarget validates the adapter's shape before adding it —
a missing/empty id, a missing emitFormat, or a non-array expectPrefixes
throws immediately, so a typo fails at register time instead of silently at
the first lookup. Re-registering an existing id overwrites it with a
console.warn, not a throw, so tests and HMR can re-register safely.
resolveConnectorIds fills in the three defaults for any key your
sorb.config.json omits.
Exports
| Export | Kind | Description | Source |
|---|---|---|---|
TIERS | value | Token tiers, most-specific first. | src/index.js:18 |
TIER_RANK | value | Tier → rank (0 = most specific). | src/index.js:24 |
DEFAULT_ROLE_IDS | value | The canonical role-id list (T0 reference = the JJ kit's semantic tier). | src/index.js:66 |
ALL_ROLE_IDS | value | Flat list of every canonical role id (all tiers), for iteration/validation. | src/index.js:89 |
resolveRole | function | Resolve a role id to the kit's actual token id via an optional override map. | src/index.js:102 |
DEFAULT_SOURCE_ID | value | Default SOURCE connector id (registered by sorb-seed). | src/index.js:107 |
DEFAULT_CODE_SOURCE_ID | value | Default CODE-SOURCE connector id (registered by sorb-juice). | src/index.js:110 |
DEFAULT_TARGET_ID | value | Default TARGET adapter id (registered by sorb-leaf). | src/index.js:113 |
connectors | value | The runtime connector registry — id → impl per axis. | src/index.js:123 |
registerSource | function | Register a SOURCE connector by its id. | src/index.js:134 |
registerCodeSource | function | Register a CODE-SOURCE connector by its id. | src/index.js:144 |
registerTarget | function | Register a TARGET adapter by its id. | src/index.js:158 |
getSource | function | Look up a registered SOURCE connector; throws on unknown id. | src/index.js:181 |
getCodeSource | function | Look up a registered CODE-SOURCE connector; throws on unknown id. | src/index.js:192 |
getTarget | function | Look up a registered TARGET adapter; throws on unknown id. | src/index.js:203 |
resolveConnectorIds | function | Resolve the three connector ids from a config, falling back to the defaults when a key is absent (back-compat = today's behavior). | src/index.js:215 |
TIERS
const TIERS = Object.freeze(['component', 'semantic', 'primitive'])
Token tiers, most-specific first. Binding precedence: component beats semantic beats primitive.
TIER_RANK
const TIER_RANK = Object.freeze({ component: 0, semantic: 1, primitive: 2 })
Tier → rank (0 = most specific). Lower wins when several tokens share a value.
resolveRole
function resolveRole(roleId, roleMap)
Resolve a role id to the kit's actual token id via an optional override map. A format calls resolveRole('color.brand', options.roleMap) → the kit's token id (identity when the kit uses canonical ids, i.e. the JJ reference).
| Parameter | Type | Description |
|---|---|---|
roleId | string | A canonical role id from ALL_ROLE_IDS. |
[roleMap] | Record<string,string> | role-id → kit-token-id overrides. |
Returns string: the kit token id to reference (var(--<kebab>)).
DEFAULT_SOURCE_ID
const DEFAULT_SOURCE_ID = 'storybook-dom'
Default SOURCE connector id (registered by sorb-seed). @type {string}
DEFAULT_CODE_SOURCE_ID
const DEFAULT_CODE_SOURCE_ID = 'local'
Default CODE-SOURCE connector id (registered by sorb-juice). @type {string}
DEFAULT_TARGET_ID
const DEFAULT_TARGET_ID = 'react-bootstrap'
Default TARGET adapter id (registered by sorb-leaf). @type {string}
connectors
const connectors = Object.freeze(
The runtime connector registry — id → impl per axis. The Maps are mutable by design so consumer packages register their defaults into them; the container itself is frozen so the axis set can't drift.
registerSource
function registerSource(conn)
Register a SOURCE connector by its id.
| Parameter | Type | Description |
|---|---|---|
conn | SourceConnector |
Returns SourceConnector: the registered connector.
registerCodeSource
function registerCodeSource(conn)
Register a CODE-SOURCE connector by its id.
| Parameter | Type | Description |
|---|---|---|
conn | CodeSourceConnector |
Returns CodeSourceConnector: the registered connector.
registerTarget
function registerTarget(adapter)
Register a TARGET adapter by its id. Minimal shape validation (T0b) — with seven+ adapters registering into one Map, a typo'd id or missing emitFormat would fail silently at query time; catch it at register time. Throws on a malformed adapter; console.warns (does not throw) on a duplicate-id overwrite so a legitimate re-register in tests/HMR still works.
| Parameter | Type | Description |
|---|---|---|
adapter | TargetAdapter |
Returns TargetAdapter: the registered adapter.
getSource
function getSource(id)
Look up a registered SOURCE connector; throws on unknown id.
| Parameter | Type | Description |
|---|---|---|
id | string |
Returns SourceConnector.
getCodeSource
function getCodeSource(id)
Look up a registered CODE-SOURCE connector; throws on unknown id.
| Parameter | Type | Description |
|---|---|---|
id | string |
Returns CodeSourceConnector.
getTarget
function getTarget(id)
Look up a registered TARGET adapter; throws on unknown id.
| Parameter | Type | Description |
|---|---|---|
id | string |
Returns TargetAdapter.
resolveConnectorIds
function resolveConnectorIds(config = {})
Resolve the three connector ids from a config, falling back to the defaults when a key is absent (back-compat = today's behavior).
| Parameter | Type | Description |
|---|---|---|
[config] | { source?: string, codeSource?: string, target?: string } |
Returns { source: string, codeSource: string, target: string: }
Types
JSDoc typedefs. Import them in your own JSDoc with import('@sorb/core').Name.
Tier
Which layer of the 3-tier DTCG taxonomy a token belongs to.
type Tier = 'primitive' | 'semantic' | 'component'
Source: src/types.js:10
TokenType
DTCG $type of the resolved token (extend as the taxonomy grows).
type TokenType = 'color' | 'dimension' | 'fontFamily' | 'fontWeight' | 'number' | 'string'
Source: src/types.js:15
TokenSet
A flat map of token name → value.
type TokenSet = Object.<string, string | number>
Source: src/types.js:20
ResolvedToken
One entry of the resolved bindable token map — the single contract produced by Style Dictionary (sorb/resolved-map) and consumed by the bridge, the capture annotator, and the plugin. This is THE shape that must not drift.
| Property | Type | Description |
|---|---|---|
id | string | Dotted token id, e.g. button.primary.bg.default. |
cssVar | string | The emitted CSS custom property, e.g. --button-primary-bg-default. |
value | string | number | Fully resolved value (no var() chains). |
tier | Tier | Taxonomy tier (drives binding precedence via TIER_RANK). |
type | TokenType | DTCG $type. |
Source: src/types.js:25
ResolvedMap
The resolved map as served/written: .sorb/resolved.json.
type ResolvedMap = ResolvedToken[]
Source: src/types.js:37
RawValue
A captured CSS value plus the raw string it came from.
| Property | Type | Description |
|---|---|---|
raw | string | The raw CSS string captured from the DOM (e.g. rgb(15,101,239)). |
Source: src/types.js:42
LayerNode
A node in a captured layer tree (Storybook story → Figma-insertable geometry). Produced by capture, annotated in place by the seed annotator, materialized by the plugin.
| Property | Type | Description |
|---|---|---|
type | string | Figma-ish node type, e.g. FRAME, TEXT. |
fills (optional) | RawValue[] | Fill paints (index 0 is the primary fill). |
strokes (optional) | RawValue[] | Stroke paints. |
cornerRadius (optional) | number | Corner radius in px. |
effects (optional) | { color?: RawValue }[] | Effects (shadows, etc.). |
children (optional) | LayerNode[] | Child nodes. |
sorb (optional) | SorbAnnotation | Token bindings attached by the annotator. |
Source: src/types.js:48
SorbAnnotation
Token bindings the seed annotator stamps onto a matched LayerNode.
| Property | Type | Description |
|---|---|---|
tokens | Object.<string, string> | role (fill/stroke/cornerRadius/effectN) → bound token id. |
candidates | Object.<string, string[]> | role → all token ids whose value matched (the plugin offers these as a switch). |
Source: src/types.js:62
StoryEntry
One story's entry in the capture index (.sorb/index.json).
| Property | Type | Description |
|---|---|---|
artifact | string | Relative path to the captured artifact (*.sorb.json). |
Source: src/types.js:71
StoryIndex
The capture index written to .sorb/index.json.
| Property | Type | Description |
|---|---|---|
stories | Object.<string, StoryEntry> | storyId → entry. |
Source: src/types.js:77
VariantSpec
Identifies a component variant by its dot-path prefix. e.g. "button.tertiary" covers all tokens whose id starts with "button.tertiary."
| Property | Type | Description |
|---|---|---|
componentId | string | Top-level component key, e.g. "button". |
variantId | string | Full dot-path of the variant, e.g. "button.tertiary". |
fromVariant (optional) | string | Dot-path of the source variant to clone from (addVariant only). |
replacedBy (optional) | string | Dot-path that replaces this variant (deprecateVariant only). |
Source: src/types.js:83
VariantChangeset
The result of a lifecycle action — what changed.
| Property | Type | Description |
|---|---|---|
action | 'add'|'deprecate' | |
variantId | string | The variant that was added or deprecated. |
tokenIds | string[] | All token ids affected (added or deprecated). |
newVersion | string | The component set's new $version after the change. |
Source: src/types.js:93
DesignUnit
A design unit to capture — one addressable thing a SourceConnector can turn into geometry (today's Storybook "story entry" is one). Opaque-ish: only id is guaranteed; connectors carry whatever extra metadata they need.
| Property | Type | Description |
|---|---|---|
id | string | Stable unit id (e.g. a Storybook story id). |
name (optional) | string | Human-readable label. |
Source: src/types.js:102
SourceConnector
SOURCE axis — where design tokens + geometry come IN. A real source pulls BOTH tokens and geometry from the tool (founder decision 2026-08-28).
| Property | Type | Description |
|---|---|---|
id | string | Registry key (default 'storybook-dom'). |
listUnits | (config: Object) => Promise<DesignUnit[]> | Discover the design units to capture. |
captureGeometry | (unit: DesignUnit, config: Object) => Promise<LayerNode> | Capture one unit as a raw (un-annotated) LayerNode tree. |
readTokens | (config: Object) => Promise<TokenSet> | Read the DTCG token set for this source. |
Source: src/types.js:111
CodeSourceConnector
CODE-SOURCE axis — where the running app / codebase lives.
| Property | Type | Description |
|---|---|---|
id | string | Registry key (default 'local'). |
resolveAppUrl | (config: Object) => Promise<string|null> | Resolve the running app's URL (today = appUrl / localhost:5173). |
resolveProjectRoot | (config: Object) => string | Resolve the project root dir (today = process.cwd()). |
provision (optional) | (config: Object) => Promise<void> | Optional: clone/build a repo → hosted preview (future code sources). |
Source: src/types.js:124
TargetAdapter
TARGET axis — how tokens bind into the running app (the component-compat seam).
| Property | Type | Description |
|---|---|---|
id | string | Registry key (default 'react-bootstrap'). |
emitFormat | string | A Style-Dictionary format id (e.g. SORB_TOKENSET). |
expectPrefixes | string[] | Vocab-guard namespace(s), e.g. ['bs-']. |
inject (optional) | (tokens: TokenSet, config: Object) => void | Optional: bind tokens into non-React hosts (the sorbInit seam). |
darkMode (optional) | DarkModeConvention | Optional: this target's dark-mode convention (real-dark-mode spec D1). Undefined ⇒ single-mode (no dark) — the target has no notion of a dark variant and mode-aware emit/inject should fall back to flat :root output. |
Source: src/types.js:136
DarkModeConvention
How a TargetAdapter's host framework expresses light/dark mode. v1 only ships 'attribute' (Bootstrap 5.3's [data-bs-theme]); 'class' (Tailwind's .dark) and 'media' (OS-only, no manual override) are named here for forward-compat but not yet implemented by any shipped adapter — see real-dark-mode-implementation spec §3 "Deferred to phase 2".
| Property | Type | Description |
|---|---|---|
strategy | 'attribute'|'class'|'media' | How the manual override is expressed. 'attribute' sets/reads a DOM attribute (e.g. data-bs-theme); 'class' toggles a class on documentElement; 'media' means OS-only, no manual override. |
attribute (optional) | string | The attribute name for strategy: 'attribute' (e.g. 'data-bs-theme'). |
darkSelector | string | The CSS selector matching the dark-mode override (e.g. '[data-bs-theme="dark"]'). |
lightSelector (optional) | string | The CSS selector matching an explicit light-mode override (e.g. '[data-bs-theme="light"]') — lets a manual "light" choice beat an OS prefers-color-scheme: dark setting. |
Source: src/types.js:150
SemanticRoles
SEMANTIC-ROLE CONTRACT (framework-targets-productization, T0). The canonical set of role ids a token kit MUST expose for the framework TargetAdapter emit formats (sorb/mantine-vars, sorb/mui-vars, sorb/mat-sys-vars, sorb/shadcn-theme, sorb/primevue-preset) to emit correctly. Each format maps its framework's own vars (--mantine-*, --mui-*, --mat-sys-*, shadcn vars, PrimeVue preset roots) ONTO these role ids. These are DTCG dot-path ids (→ CSS var --<kebab> → preview-payload key <kebab>). The Janes Jeans kit (@metatoy/janes-jeans) is the reference implementation. A kit using different names supplies options.roleMap (role-id → its-own-token-id) to a format rather than forking it. SEMVER: adding a role id here is a MINOR bump; renaming or removing one is a MAJOR bump for @sorb/core AND @sorb/seed — every format consumer depends on this set. Scope = the UNION of the target maps' role columns, not a kit's full token tree; anything beyond this list is kit-private.
| Property | Type | Description |
|---|---|---|
color | string[] | surface/ink/brand/accent/danger/success/border/focus roles. |
radius | string[] | control/card/pill. |
shadow | string[] | raised/overlay. |
typography | string[] | display/heading/body/caption × fontSize/Weight/lineHeight. |
Source: src/types.js:172
ConnectorRegistry
The runtime connector registry — id → impl per axis. The Maps are mutable by design so consumer packages register their defaults into them; the container itself is frozen so the axis set can't drift.
| Property | Type | Description |
|---|---|---|
source | Map<string, SourceConnector> | |
codeSource | Map<string, CodeSourceConnector> | |
target | Map<string, TargetAdapter> |
Source: src/types.js:198
Next
@sorb/seed— registers the default source connector; formatoptions.roleMapresolves against this page's role ids.@sorb/leaf— registers the seven target adapters into this registry.@sorb/juice— the bridge that servesResolvedToken[]atGET /tokens/resolved.
Works with Figma. Not affiliated with, or endorsed by, Figma. Figma is a trademark of Figma, Inc.