Styles
A site’s look is a library of named styles. You create them, name them in your own words (“Överrubrik”, “Hero title”, “Price note”) and reuse them. Change a style once and every page using it follows.
There are two kinds:
- Site-wide styles for body text, Heading 1–6 and links. They apply to every matching element automatically, on every page.
- Styles chosen per block, such as an eyebrow, a lead paragraph, a quote, a button or section spacing. Pick them in a block’s Style field.
See Design tokens for palette-first block fields, named scales, auditing and migration (prepared for Core 0.2.106).
Start with the standard styles
Section titled “Start with the standard styles”New sites start with a considered standard set: body text, Heading 1–6, link, lead, eyebrow, small text, quote, primary and secondary button, and section spacing. Sizes and spacing are set for phones, tablets and desktops.
Setting these to match the brand is the natural first step of a new site, before building pages. For an existing site, Styles → Add standard styles adds the ones it is missing and never changes styles you already have.
Edit a style
Section titled “Edit a style”Open Styles in the site menu. Each style has:
- Name and When to use it, which editors and AI agents see.
- Class, a stable id (
s-<class>) for custom CSS. It cannot change later. - Can be chosen for: text, headings, lists, buttons, sections and containers, links or images. Links and images are chosen for block parts, such as the links in a formatted heading or a hero’s image.
- Block parts: parts of blocks this style applies to on every block of that type (see below).
- Values per screen size. Mobile applies everywhere. Tablet, Laptop, Desktop and Wide override from that width up. An empty field keeps the smaller screen’s value, shown as a hint.
- Font, size, weight, line height, letter spacing, case, italic, text and background colour, alignment, underline, spacing before and after, padding, max width, corner radius, border, opacity, shadow and gap.
- Advanced CSS, scoped to the style: bare declarations, or rules where
&stands for the style, such as&::before { content: "—" }.
| Property | Values |
|---|---|
opacity |
A number from 0 (invisible) to 1 (opaque), such as 0.8. |
shadow |
none, sm, md or lg. none removes a shadow the block draws itself, such as the inset ring of an outline button. |
gap |
Space between the children of a flex or grid element: one length (1rem), or row and column (1rem 2rem). Useful for a block’s layout parts, such as a hero’s row of buttons. |
The preview shows the style at the selected screen width with the site’s own colours. Fonts load on the site itself.
Colours are best chosen from the palette (primary, secondary, accent,
background, surface, text, text light). A palette change then flows through
every style. on primary, on secondary and on accent are the text colour
on that palette colour: the site’s own choice when one is set (see
Text on theme colours), else black or
white, whichever reads better.
Site-wide link hover
Section titled “Site-wide link hover”The link role supports hover, for example
{ "color": "primary", "decoration": "underline" }. It generates a normal
a:hover rule for links throughout the site, including prose, breadcrumbs,
the table of contents and the consent dialog’s privacy link. More specific
block and part hover rules (buttons, navigation and cards) retain precedence.
No hover values means no additional hover rule. Palette names are supported.
Available from Core 0.2.121. Existing static sites need to be published again after upgrading. Touch-only devices do not have a persistent mouse hover state.
Readable text
Section titled “Readable text”Every style’s text is measured against its background at every screen size, including hover colours, with two measures:
- APCA (Accessible Perceptual Contrast Algorithm, APCA-W3 0.0.98G-4g), a
lightness contrast
Lcthat models how people perceive brightness and polarity. It decides. - The WCAG 2 contrast ratio, which WCAG 2.x AA conformance (and EN 301 549) uses: 4.5:1, or 3:1 for large text (24px, or 18.7px bold).
The WCAG 2 ratio overrates dark text on mid-dark saturated colours. On teal
#00a19c, #111827 is 5.56:1 but APCA Lc 44, while white is 3.19:1 but
Lc 63.9, and white reads better.
The APCA level a text needs depends on how it is read, its size and its weight. Typeroll uses the APCA Readability Criterion Bronze levels, with the size and weight minimums of the APCA guidance for non-body text:
| Text | APCA Lc |
|---|---|
| Body text: body, lead, quote, link and section styles; text and list styles | 75 |
| Secondary text: the small text style (notes, captions, fine print) | 60 |
| Headings, eyebrows and buttons from 16px at weight 600, 21px at 500 or 24px | 60 |
| Headings, eyebrows and buttons from 24px at weight 700, or 36px | 45 |
| Smaller or lighter headings, eyebrows and buttons | 75 |
Body text under 14px, and any text at weight 300 or lighter, needs the next level (Lc 15 more, up to 90). A style without a standard role counts as a heading or button only when it can be chosen for headings and buttons alone. Sizes are read from the style (px, rem or em); without a size, 16px is assumed, and without a weight, 400 (700 for headings). Lc is compared after rounding to a whole number.
- Refused: text that misses its APCA level and WCAG 2 AA. Light grey on white is never accepted.
- Saved with a warning: text that misses its APCA level but meets WCAG 2 AA. Every style accepted before APCA stays accepted.
- Accepted: text that meets its APCA level, such as white 16px semibold
button text on
#00a19c. Its WCAG 2 ratio can be lower than 4.5:1: a site that must conform to WCAG 2.x AA should keep that ratio, shown next to each colour in Settings and in every message.
Messages give both measures, for example “#111827 on #00a19c is APCA Lc
44.0, WCAG 2 5.56:1”. The API and MCP return warnings as warnings.
When standard styles are added to a site whose palette colour is unreadable
on the background (it fails both measures), those styles use the body text
colour instead, because adding them unchanged would be refused. The result
lists them in adjusted and says in warnings which colour was replaced and
why, with both measures. Existing styles are never changed.
Apply a style to a block
Section titled “Apply a style to a block”Heading, formatted heading, text, list, button, section and container blocks have a Style field (Heading style on a heading) listing the styles made for that kind of block. Choose Default look to use the block’s own appearance. A heading’s eyebrow and subtitle have their own style fields; left on Site default, they use the site’s Eyebrow and Lead styles (render version 2).
The style is applied to the meaningful element: the <h2> in a heading block,
the link in a button. A chosen style wins over the block’s own default
appearance for the properties it sets.
Style parts of a block
Section titled “Style parts of a block”Blocks draw some of their elements with a fixed look of their own: the
underline on links in a formatted heading, the size of a hero’s heading, the
corners of its buttons. A part is such an element, named on the block type:
link in a formatted heading, heading, primary_button and media in a
hero. A style can be given to a part, so you change that look with a named
style instead of CSS against the block’s markup.
Each part has a kind, and only styles that can be chosen for that kind are
offered: a link part takes link styles, a hero’s image takes image styles. The
parts of the core blocks are listed in
Block parts; read_block_type lists the parts of any
block type.
Apply a style to a part on one block
Section titled “Apply a style to a part on one block”Select the block and open Appearance → Part styles. Each part has a picker with the styles of its kind; Site default keeps the site-wide style for the part (named in the option when there is one), or else the block’s own look.
Through MCP or the API, set the block’s part_styles field, a map from part
name to style id:
update_block page_id=… block_id=… data={ part_styles: { link: "quiet-link" } }update_block replaces the whole map, so send every part you want to keep;
null removes it. A heading’s eyebrow, heading text and subtitle, and a
button’s link, are parts too, but they keep their own style fields
(eyebrow_style_id, style_id, subtitle_style_id) instead of
part_styles.
Apply a style to a part on every block
Section titled “Apply a style to a part on every block”In a style’s Block parts, tick the parts it applies to, for example
Formatted heading · Links. The list shows the parts of the kinds the style
can be chosen for, grouped by block, and says which other style already holds
a part. Through MCP or the API it is the style’s parts, as
<block type>.<part>:
create_style id="quiet-link" name="Quiet link" targets=["link"] base={ decoration: "none" } parts=["core/rich_heading.link"]A part takes one site-wide style. The style’s Can be chosen for must
include the part’s kind, and a part the site’s block types do not have is
refused with the closest part names. Element roles (body, Heading 1–6, link)
already style every matching element and cannot list parts. update_style
replaces parts; [] or null removes the style from every part.
Which style wins
Section titled “Which style wins”From lowest to highest:
- The block’s own look, including its field values.
- The default role style, such as the site’s Eyebrow style on an eyebrow.
- The part style: the block’s own Part styles choice, else the site-wide style for the part. A block’s choice replaces the site-wide style entirely; nothing of the site-wide style remains on that block.
- A style class on the element itself: a heading’s eyebrow style, a button’s style, or a paragraph style from the text editor.
A part style sets only the properties it has values for. It wins over the block’s own fields for those properties, as a chosen style always does: to look different on one block, choose another style for the part there.
Readable parts
Section titled “Readable parts”A part style’s text is measured against the part’s background with the same APCA and WCAG 2 levels as other styles. The background is the block’s own background colour where the block has one (a hero’s Background), the colour the block paints behind the part (a primary button on primary), or else the site background. Parts without text of their own, such as a hero’s overlay or button row, are not measured.
- Choosing a style for a part on one block is refused when its text fails
both APCA and WCAG 2 AA there (
part_style_contrast), and saved with a warning when it misses only its APCA level. - A style that lists parts is measured on each part’s background when it is saved.
Styles and custom CSS
Section titled “Styles and custom CSS”Site-wide body, heading and link styles set variables that the platform’s own
CSS reads. Other styles are classes (s-<class>). Write custom CSS against
those classes instead of platform internals such as [data-block] or
.block-*, which can change between render versions. To change an element a
block draws itself, such as the underline on a formatted heading’s links, give
its part a style instead.
Site CSS and page CSS
Section titled “Site CSS and page CSS”For what styles cannot express, there are two managed CSS fields:
- Site CSS (Styles → Site CSS, admins) loads on every page after the
styles. Through MCP or the API it is
custom_cssinupdate_site_settings. - Page CSS (page editor → page settings → Page CSS) loads after the site
CSS on that page only, and saves with the page draft. Through MCP or the API
it is
custom_cssonupdate_page.
Both editors check the CSS as you type and show line numbers:
- Errors prevent saving: unbalanced braces, an unclosed comment or string,
declarations outside a rule,
expression(),javascript:URLs and anything that would end the stylesheet early. The API refuses the same writes. - Warnings allow saving: selectors on platform markup such as
[data-block]or.block-…, which can change between render versions, and@import, which delays the page. The API returns them aswarnings(settings) orcss_warnings(pages).
The editors offer the site’s style classes and the CSS classes set on the
page’s blocks as selectors to insert. Prefer them: .s-eyebrow or
.pricing-note keeps working when platform markup changes.
Through MCP and the API
Section titled “Through MCP and the API”| Tool | REST | Purpose |
|---|---|---|
list_styles |
GET /api/v1/sites/{siteId}/styles |
The library, missing standard roles and problems |
create_style |
POST /api/v1/sites/{siteId}/styles |
Create a style |
update_style |
PATCH /api/v1/sites/{siteId}/styles/{id} |
Update; properties merge, null removes one |
delete_style |
DELETE /api/v1/sites/{siteId}/styles/{id} |
Delete; blocks fall back to their default look |
apply_standard_styles |
POST /api/v1/sites/{siteId}/styles/standard |
Add missing standard styles (overwrite resets) |
Set a block’s style with its style_id field, for example
update_block … data: { style_id: "eyebrow" }, a part’s style on one block
with data.part_styles, and a part’s style on every block with the style’s
parts (see Style parts of a block). Styles are
versioned with the
site settings, so a branch can try new styles without changing the live site.
AI agents follow the same rule as people: a look that could recur becomes a named style, and only a clearly unique one-off uses block settings.
Heading roles (h1–h6) support space_before and space_after at the base and
at each style breakpoint. Use these for consistent vertical rhythm, rather than
adding a spacer to every page. Whole-tree writes check global part styles as
well as per-instance styles against known ancestor background colors. These
checks do not replace browser review of images, gradients or custom CSS.