Linked CSS Variable Presets
Several inputs in the page builder support linked CSS variable presets — instead of hardcoding values like font-size: 32px or border-radius: 8px, the builder inserts CSS variable references. This keeps content in sync with theme settings: when a company changes their heading size, brand color, spacing, or corner radius, all linked values update automatically.
How values are stored: When a linked preset is used in the page builder, the persisted value is the full var() form — for example border-radius: var(--corner_radius_md) or color: var(--color_primary). It is not stored as a bare custom property token like --corner_radius_md alone (that would not be valid in a CSS property value). In theme.liquid you still declare properties as --name: …; the storefront and saved builder content reference them as var(--name).
Linked presets are available in:
- Rich text editor — text size, font family, font weight, and color presets
- Corner radius inputs — corner radius presets
- Padding inputs — spacing/padding presets
Table of Contents
- Linked CSS Variable Presets
How It Works
When a company selects a linked preset in the rich text editor, the output uses CSS variable references wrapped in var(). Size, font family, and font weight each link independently, so any of them can be a var():
<span style="font-size: var(--font_size_h1); font-family: var(--font_family_heading); font-weight: var(--font_weight_heading);"> Welcome to our store </span> <span style="color: var(--color_primary);"> Shop now </span>
If the company unlinks and switches to manual controls, the resolved values are applied directly instead:
<span style="font-size: 32px; font-family: Montserrat; font-weight: bold;"> Welcome to our store </span> <span style="color: #2563eb;"> Shop now </span>
For this to work, your theme needs two things: settings defined in settings_schema.json and CSS variables wired in theme.liquid.
Setup
Step 1: Define Settings in settings_schema.json
The builder looks for settings in specific groups by name. Settings in these groups become available as presets:
| Group Name | What It Powers |
|---|---|
typography | Font size presets in the rich text editor |
color_schema | Color presets in the rich text editor |
corner_radius | Corner radius presets |
padding | Padding/spacing presets |
custom_font_sizes | Additional custom text size presets (optional) |
custom_colors | Additional custom color presets (optional) |
Font family and font weight are resolved per text-size preset (see How Text Size Presets Are Built). A preset can link them in two ways: by role — font_family_heading / font_family_body are picked up by setting ID from any group — or explicitly, via font_family_ref / font_weight_ref pointing at any setting ID. Font-family pickers and weight settings are only read as sources; they are never turned into presets themselves, so they can live anywhere (a separate fonts group keeps things tidy).
Example typography and font settings:
The
font_family_ref/font_weight_reffields below link each size preset's family and weight to a specific setting (and its CSS variable). Omit them to fall back to the role-derivedfont_family_heading/font_family_bodyand thebold/normaldefaults.
[ { "name": "fonts", "settings": [ { "type": "font_picker", "id": "font_family_heading", "label": "Heading Font", "default": "Montserrat" }, { "type": "font_picker", "id": "font_family_body", "label": "Body Font", "default": "Inter" }, { "type": "range", "id": "font_weight_heading", "label": "Heading Weight", "min": 100, "max": 900, "step": 100, "default": 700 }, { "type": "range", "id": "font_weight_body", "label": "Body Weight", "min": 100, "max": 900, "step": 100, "default": 400 } ] }, { "name": "typography", "settings": [ { "type": "range", "id": "font_size_h1", "label": "Heading 1 Size", "min": 16, "max": 80, "step": 1, "default": 40, "unit": "px", "role": "heading", "font_family_ref": "font_family_heading", "font_weight_ref": "font_weight_heading" }, { "type": "range", "id": "font_size_body", "label": "Body Size", "min": 12, "max": 32, "step": 1, "default": 16, "unit": "px", "role": "body", "font_family_ref": "font_family_body", "font_weight_ref": "font_weight_body" } ] } ]
Example color settings:
{ "name": "color_schema", "settings": [ { "type": "color", "id": "color_primary", "label": "Primary Color", "default": "#2563eb" }, { "type": "color", "id": "color_body", "label": "Body Text Color", "default": "#1a1a2e" } ] }
Step 2: Wire Settings to CSS Variables in theme.liquid
Inside a {%- style -%}...{%- endstyle -%} or <style>...</style> block in layouts/theme.liquid, define CSS custom properties that reference your settings:
{'%' style '%'}
:root {
/* Typography */
--font_size_h1: {{ settings.font_size_h1 | append: 'px' }};
--font_size_h2: {{ settings.font_size_h2 | append: 'px' }};
--font_size_h3: {{ settings.font_size_h3 | append: 'px' }};
--font_family_heading: {{ settings.font_family_heading | font_family }};
--font_family_body: {{ settings.font_family_body | font_family }};
--font_weight_heading: {{ settings.font_weight_heading }};
--font_weight_body: {{ settings.font_weight_body }};
/* Colors */
--color_primary: {{ settings.color_primary }};
--color_body: {{ settings.color_body }};
--color_heading: {{ settings.color_heading }};
/* Corner Radius */
--corner_radius_sm: {{ settings.corner_radius_sm | append: 'px' }};
--corner_radius_md: {{ settings.corner_radius_md | append: 'px' }};
--corner_radius_lg: {{ settings.corner_radius_lg | append: 'px' }};
/* Padding / Spacing */
--spacing_sm: {{ settings.spacing_sm | append: 'px' }};
--spacing_md: {{ settings.spacing_md | append: 'px' }};
--spacing_lg: {{ settings.spacing_lg | append: 'px' }};
}
{'%' endstyle '%'}
If a setting in a recognized group has a matching CSS variable in theme.liquid, the preset becomes linkable in the builder.
Responsive Values
Because linked presets resolve to var() references, the value a preset renders is whatever the CSS variable holds at that point in the cascade. Redefine the variable inside a media query and every linked value updates at that breakpoint automatically — the saved builder content does not change, because it still reads var(--token).
Declare a base value in :root and override it inside a @media block in theme.liquid:
:root { --gap: 12px; } @media (min-width: 768px) { :root { --gap: 24px; } }
Any content linked to var(--gap) renders 12px below 768px and 24px at or above it. The same approach works for any wired token — typography, color, corner radius, or padding — so a single linked preset adapts across breakpoints from one declaration in theme.liquid.
How Text Size Presets Are Built
Every size setting in the typography group becomes one entry in the rich text editor's Text Presets dropdown. Each preset has three dimensions — size, font family, and font weight — and each dimension is resolved (and linked to a CSS variable) independently.
Only size settings become presets. Settings typed
font_picker/font_family/font(font-family pickers) andfont_weightare skipped when building presets — they are read as sources for a preset's family/weight, never turned into presets themselves. A font picker sitting in thetypographygroup is therefore harmless and does not create a phantom preset. Grouping pickers and weights under a separatefontsgroup (as in the example above) is still the clearest layout.
Resolving each dimension
For every size setting, the builder resolves family, weight, and size using this precedence:
Font family — explicit ref → per-setting literal → role-derived theme font:
font_family_ref→ resolves from the referenced setting and links to that setting's CSS variable (e.g.var(--font_family_heading)).font_family(a literal value on the setting) → applied as a concrete value, not linked.- Otherwise the role decides: a heading uses
font_family_heading, body usesfont_family_body(linked to their vars when present). Heading and body fall back to each other if only one is defined; if neither exists the picker shows "Inherited" and no family is applied (text inherits the surrounding theme CSS).
Font weight — explicit ref → per-setting literal → role default:
font_weight_ref→ resolves from the referenced setting and links to its CSS variable (e.g.var(--font_weight_heading)). The referenced setting is resolved by ID regardless of its type, so a weight authored as arangeworks.font_weight(a literal) → applied as a concrete value.- Otherwise the role default:
boldfor headings,normalfor body.
Font size — the setting's own value, suffixed with its unit, linked to the setting's CSS variable when one exists.
How role is determined
A setting's role (heading vs body) drives the default family and weight:
- An explicit
rolefield ("heading"or"body") on the setting takes precedence. - Otherwise the builder falls back to the legacy heuristic: an ID that starts with
font_size_his a heading (a loose prefix match —font_size_hero/font_size_headeralso count), everything else is body.
Prefer the explicit role field for new themes; the font_size_h* prefix is only a fallback so existing themes keep working.
Value precedence
Each dimension's value resolves through this chain:
live builder value (unsaved edits in the Theme panel) → saved value (the merchant's current setting value) → schema
default→ hardcoded fallback (16pxfor size,#000000for color)
In the builder, the preset list reflects in-flight Theme-panel edits immediately, so the dropdown matches the live preview before you save. Saved storefront content uses the saved value.
Units
The size is emitted with the setting's unit (defaulting to px when omitted), and decimal values are preserved — so rem/em sizes such as 2.5rem work. (min / max / step are not part of the resolved value; they only constrain the slider.)
Corner Radius Presets
Corner radius inputs support linked CSS variable presets. Define corner radius settings in the corner_radius group and map them to CSS variables in theme.liquid.
settings_schema.json:
{ "name": "corner_radius", "settings": [ { "type": "range", "id": "corner_radius_sm", "label": "Small", "min": 0, "max": 24, "step": 1, "default": 4, "unit": "px" }, { "type": "range", "id": "corner_radius_md", "label": "Medium", "min": 0, "max": 32, "step": 1, "default": 8, "unit": "px" }, { "type": "range", "id": "corner_radius_lg", "label": "Large", "min": 0, "max": 48, "step": 1, "default": 16, "unit": "px" } ] }
When a preset is selected, the saved value is the var() reference (e.g. var(--corner_radius_md)), not the bare name --corner_radius_md. The input shows the preset label in read-only mode. When corners are linked, the preset applies to all four corners.
Padding Presets
Padding inputs support the same linked preset pattern using the padding group name.
settings_schema.json:
{ "name": "padding", "settings": [ { "type": "range", "id": "spacing_sm", "label": "Small", "min": 0, "max": 48, "step": 1, "default": 8, "unit": "px" }, { "type": "range", "id": "spacing_md", "label": "Medium", "min": 0, "max": 64, "step": 1, "default": 16, "unit": "px" }, { "type": "range", "id": "spacing_lg", "label": "Large", "min": 0, "max": 96, "step": 1, "default": 32, "unit": "px" } ] }
When a preset is selected, the saved value uses var(--token) on the selected side(s) (for example padding-top: var(--spacing_md)), not bare --spacing_md. When sides are linked, the preset applies to all four sides.
How the Mapping Works
The admin parses theme.liquid and matches this pattern:
--{css_var_name}: {{ settings.{setting_id} ...
It builds a map of setting_id to css_var_name (the --… name from the left-hand side of the declaration). When the builder saves a linked preset, it writes that token inside var() in the stored style (e.g. mapping color_primary → --color_primary results in color: var(--color_primary) in saved content).
Line in theme.liquid | Detected Mapping |
|---|---|
--font_size_h1: {{ settings.font_size_h1 | append: 'px' }}; | font_size_h1 → --font_size_h1 |
--color_primary: {{ settings.color_primary }}; | color_primary → --color_primary |
--font_family_heading: {{ settings.font_family_heading | font_family }}; | font_family_heading → --font_family_heading |
--font_weight_heading: {{ settings.font_weight_heading }}; | font_weight_heading → --font_weight_heading |
Naming Convention
The CSS variable name does not need to match the setting ID. All of these work:
--font_size_h1: {{ settings.font_size_h1 | append: 'px' }}; --heading-size-1: {{ settings.font_size_h1 | append: 'px' }}; --h1-size: {{ settings.font_size_h1 | append: 'px' }};
The parser matches on the settings.{id} part, not the variable name. However, keeping names consistent (e.g., --font_size_h1 for settings.font_size_h1) is recommended for clarity.
What Gets Linked and What Doesn't
Not everything becomes a linked preset:
- Custom presets (from
custom_font_sizes/custom_colorsgroups) — when you add a custom preset in the builder it is linked, not baked in: the builder injects a--<id>: {{ settings.<id> }}declaration intotheme.liquidand the preset resolves to that variable, exactly like a built-in preset. A custom text preset carries itsfont_familyandfont_weightas concrete literals (it does not use the heading/body role), so it links its size to a variable while applying its family and weight as fixed values. - Settings without a CSS variable in
theme.liquid— if a setting exists insettings_schema.jsonbut has no corresponding--var: {{ settings.id }}line intheme.liquid, it will still appear as a preset but will apply the resolved value directly. - Font weight — linkable. A weight resolved from a
font_weight_reflinks to that setting's CSS variable (e.g.var(--font_weight_heading)); a weight from a literalfont_weightor the role default (bold/normal) is applied as a concrete value.
Default Behavior (Linked Presets)
The toolbar shows two preset pickers: Text Presets and Color Presets. Each preset in the dropdown corresponds to a theme setting. Selecting a preset writes inline styles using var(--setting_token) (always the var() wrapper), matching the rich text, corner radius, and padding behavior above.
Unlinking
Each preset picker has an "Unlink Variables" option in its footer. Clicking it:
- Resolves any active CSS variable values back to their current concrete values
- Switches the toolbar to manual mode showing individual controls: Font Family picker, Font Size picker, and Color picker
The toolbar has a "Link Variables" option to switch back to preset mode.