Skip to content
BetaDocs

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).

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.

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.

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.

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 Lc that 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.

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.

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.

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.

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.

From lowest to highest:

  1. The block’s own look, including its field values.
  2. The default role style, such as the site’s Eyebrow style on an eyebrow.
  3. 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.
  4. 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.

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.

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.

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_css in update_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_css on update_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 as warnings (settings) or css_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.

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.