Every token produces a CSS custom property. The format you choose determines whether it also registers as a WordPress preset (visible in the Site Editor).
For preset categories (color, gradient, shadow, fontFamily, fontSize), a string value registers as a WordPress preset. The slug and name are auto-derived from the key:
{
"tokens": {
"color": {
"primary": "#0073aa",
"secondary": "#23282d"
}
}
}Use object syntax when you need to override slug, name, or add other properties:
{
"tokens": {
"color": {
"primary": { "value": "#0073aa", "name": "Primary Brand Color" },
"secondary": { "value": "#23282d" }
}
}
}cssOnly: true means "emit as a CSS variable only, and never expose this token to WordPress." The contract is the same across every category:
- Excluded from every theme.json preset array (
settings.color.palette,settings.spacing.spacingSizes,settings.typography.fontFamilies,settings.typography.fontSizes,settings.shadow.presets) - Excluded from
settings.custom.*(including custom-only categories likefontWeight,lineHeight,radius,transitionand the custom portion ofshadow) - Excluded from the
--wp--preset--*/--wp--custom--*fallback mapping intokens.wp.csswhenthemeable: true - Still emitted as a
--{prefix}--{segment}-{key}variable intokens.css - Still resolvable from
baseStylesin the SCSS output (it emits the CSS variable reference); in theme.jsonstylesthe generator falls back to the underlying raw value
Explicit flag — cssOnly: true on an object entry. Works in any category, preset-capable or custom-only:
{
"tokens": {
"color": {
"primary": "#0073aa",
"primary-hover": { "value": "#005a87", "cssOnly": true }
},
"fontWeight": {
"normal": "400",
"black": { "value": "900", "cssOnly": true }
},
"shadow": {
"card": "0 1px 3px rgba(0,0,0,0.1)",
"focus-ring": { "value": "0 0 0 3px rgba(0,115,170,0.4)", "cssOnly": true }
}
}
}After running the generator:
primary-hover,black, andfocus-ringall exist as CSS variables (--mylib--color-primary-hover,--mylib--font-weight-black,--mylib--shadow-focus-ring).- None of them appear anywhere in
theme.json— not in the preset arrays, not insettings.custom. - The Site Editor cannot see or override them.
String shorthand in custom-only categories — For fontWeight, lineHeight, radius, transition, zIndex, a string value registers a normal token that still appears in settings.custom.*. Those categories have no WordPress preset mapping, but they do emit --wp--custom--* variables by default. Add cssOnly: true if you want to keep a specific token out of settings.custom as well:
{
"tokens": {
"fontWeight": {
"normal": "400",
"bold": "700",
"black": { "value": "900", "cssOnly": true }
}
}
}Here normal and bold land in settings.custom.fontWeight, but black is emitted only as --mylib--font-weight-black in tokens.css.
Fluid font sizes generate responsive clamp() values. Use the shorthand { "min": "...", "max": "..." } directly:
{
"tokens": {
"fontSize": {
"small": { "min": "0.875rem", "max": "1rem" }
}
}
}The shorthand expands internally to { "value": "1rem", "fluid": { "min": "0.875rem", "max": "1rem" } } — value is auto-derived from max, which becomes the non-fluid fallback and the size emitted to theme-{prefix}.json. You can also write the nested form explicitly ({ "fluid": { "min", "max" } }) and omit value — the same auto-derivation applies. Provide value explicitly only when you want a different fallback than max.
| Property | Required | Description |
|---|---|---|
value |
Yes* | The CSS value. *Auto-derived from fluid.max for fluid font sizes |
name |
No | Human-readable label (auto-derived from key) |
slug |
No | WordPress preset slug (auto-derived from key) |
cssOnly |
No | When true, emit as a CSS variable only and exclude from every WordPress output — not a preset, not a settings.custom.* entry, not overridable in the Site Editor |
fluid |
No | { min, max } for fluid fontSize tokens |
fontFace |
No | Font file definitions for fontFamily tokens |
For object entries (without cssOnly), slug and name are derived from the token key:
- slug: Uses the key directly (
"primary"→ slug"primary") - name: Title-cases the key (
"x-large"→ name"X Large")
Override when needed:
{
"tokens": {
"spacing": {
"md": { "value": "1rem", "slug": "40", "name": "Medium" }
}
}
}Every token defined in c2b.config.json generates a CSS custom property using your project prefix (e.g. --rds--color-primary). This variable works everywhere — Storybook, Next.js, WordPress — and is the one your components should reference.
Categories that map to WordPress presets also generate a corresponding --wp--preset--* variable through theme.json. These power the Site Editor UI (color pickers, spacing controls, font selectors) and are managed by WordPress — you never reference them directly in your components.
Categories without a WordPress preset instead appear under settings.custom in theme.json, which generates --wp--custom--* variables. These exist so theme.json styles can reference the values, but again, your components use the prefixed variable.
| Category | --your-prefix--* |
--wp--preset--* |
--wp--custom--* |
|---|---|---|---|
| color | ✓ | ✓ | |
| gradient | ✓ | ✓ | |
| spacing | ✓ | ✓ | |
| fontFamily | ✓ | ✓ | |
| fontSize | ✓ | ✓ | |
| shadow | ✓ | ✓ | |
| fontWeight | ✓ | ✓ | |
| lineHeight | ✓ | ✓ | |
| radius | ✓ | ✓ | |
| transition | ✓ | ✓ | |
| layout | ✓ | ||
| zIndex | ✓ |
layout maps directly to settings.layout in theme.json rather than a preset or custom value. zIndex is CSS-only and excluded from theme.json entirely.
These appear in the WordPress Site Editor controls:
| Category | CSS Variable | WordPress Mapping | Editor UI |
|---|---|---|---|
color |
--prefix--color-* |
settings.color.palette |
Color picker |
gradient |
--prefix--gradient-* |
settings.color.gradients |
Gradient picker |
spacing |
--prefix--spacing-* |
settings.spacing.spacingSizes |
Spacing controls |
fontFamily |
--prefix--font-family-* |
settings.typography.fontFamilies |
Font picker |
fontSize |
--prefix--font-size-* |
settings.typography.fontSizes |
Size picker |
shadow |
--prefix--shadow-* |
settings.shadow.presets |
Shadow picker |
layout |
--prefix--layout-* |
settings.layout |
Layout controls |
These produce CSS variables and go under settings.custom in theme.json (no editor UI):
| Category | CSS Variable | theme.json |
|---|---|---|
fontWeight |
--prefix--font-weight-* |
settings.custom.fontWeight |
lineHeight |
--prefix--line-height-* |
settings.custom.lineHeight |
radius |
--prefix--radius-* |
settings.custom.radius |
transition |
--prefix--transition-* |
settings.custom.transition |
zIndex |
--prefix--z-* |
Excluded from theme.json |
Every token becomes a CSS custom property with static values:
:root {
/* Colors */
--mylib--color-primary: #0073aa;
--mylib--color-primary-hover: #005a87;
/* Font Sizes (fluid) */
--mylib--font-size-small: clamp(0.875rem, 0.875rem + ((1vw - 0.2rem) * 0.208), 1rem);
/* Font Weights */
--mylib--font-weight-normal: 400;
--mylib--font-weight-bold: 700;
/* Layout */
--mylib--layout-content-size: 768px;
}Only generated when output.themeable: true. Preset entries map to WordPress preset variables with the original value as a fallback. CSS-only tokens stay hardcoded:
:root {
/* Object entry — maps to WP preset, overridable via Site Editor */
--mylib--color-primary: var(--wp--preset--color--primary, #0073aa);
/* cssOnly flag — hardcoded, not overridable */
--mylib--color-primary-hover: #005a87;
/* String shorthand — also hardcoded */
--mylib--font-weight-bold: 700;
/* Fluid font sizes use clamp() as fallback */
--mylib--font-size-small: var(--wp--preset--font-size--small, clamp(0.875rem, ...));
}| Scenario | File | Behavior |
|---|---|---|
| Storybook / React app | tokens.css |
All values hardcoded |
| WordPress — locked design system | tokens.css |
Components ignore Site Editor changes |
| WordPress — themeable | tokens.wp.css |
Preset tokens follow Site Editor; CSS-only tokens stay locked |
Reference the generated CSS variables in component SCSS:
.mylib-card {
background-color: var(--mylib--color-background);
border: 1px solid var(--mylib--color-border);
border-radius: var(--mylib--radius-lg);
padding: var(--mylib--spacing-md);
font-size: var(--mylib--font-size-medium);
}The variable pattern is always --{prefix}--{css-segment}-{key}.
- Edit
c2b.config.json - Run
npx c2b generate - All outputs update —
tokens.css,tokens.wp.css,theme.json
To add a new token, add it to the appropriate category and run the generator. Then reference --prefix--{category}-{key} in your component CSS.
Use string shorthand for preset categories — it registers as a preset automatically. Use cssOnly for tokens that should be CSS variables only (hover states, borders). Use string shorthand for custom categories (fontWeight, lineHeight, radius, transition, zIndex) — they are always CSS-only:
{
"tokens": {
"color": {
"primary": "#0073aa",
"primary-hover": { "value": "#005a87", "cssOnly": true },
"secondary": "#23282d",
"secondary-hover": { "value": "#1a1e21", "cssOnly": true }
},
"fontWeight": {
"normal": "400",
"bold": "700"
}
}
}Here, primary and secondary appear in the Site Editor palette (string shorthand = preset for preset categories). primary-hover and secondary-hover are CSS variables only — they won't clutter the editor UI.