Shopify Builds>LUS Brands>Rich-text section rebuilt as a per-block typography and highlight system

Rich-Text Section Rebuilt as a Per-Block Typography and Highlight System

Dawn's rich-text.liquid, kept under its own name and rebuilt inside: every heading, text and button block writes its own type, spacing and colour tokens as CSS custom properties, and the [[word]] highlight and the button become snippets other sections share.

Dawn's rich-text section offers three preset heading sizes and no per-block spacing, and a Shopify setting holds one value where a responsive design needs two. We rebuilt the section under Dawn's own name so every heading, text and button block emits its own desktop and mobile type, spacing and colour tokens as CSS custom properties, and moved the [[word]] highlight and the button into documented snippets other sections now render.

The Problem

Dawn's stock rich-text section gives a heading block a choice of three preset sizes and gives a text block none. There's no per-block spacing, no weight, no line-height, no mobile variant of any of it, and no way to colour one word inside a headline. That's fine for a paragraph on a policy page. It isn't fine for a brand campaign page, where the comp specifies a size, a weight and a line-height for each heading and each paragraph, different again on a phone, with the brand's accent colour on one word.

On this storefront every campaign page was otherwise one of two things: a developer ticket for a one-off stylesheet, or a merchandiser pasting inline HTML into a text setting to fake the type — which works until the next theme update, and reads as a hack to anyone who opens the editor.

The Constraint

Everything had to be authorable in the theme editor, and Shopify has no setting type that holds a stylesheet, so the design had to come out of typed settings rather than a free-form CSS field. The work had to stay inside Dawn's section-and-block model — Dawn's rte and inline-richtext classes, its scroll-reveal cascade keyed on --animation-order, its page-width container and settings.page_width — so that the rest of the theme kept treating this as a rich-text section.

One section file had to serve three page types with three different type scales: the homepage, the adult about page and the kids about page. And a Shopify range setting holds one number, so "40px on desktop, 30px on mobile" means two settings and two tokens for every property that changes at the breakpoint.

What We Built

rich-text.liquid keeps Dawn's schema name, its t: locale keys and three of its block types — heading, text, button — and almost nothing else of Dawn's body. A scoped {% style %} block loops section.blocks and, for each one, writes a rule keyed to the block's own id. A heading block's rule on .rich-text__heading--{{ block.id }} carries --font-h2--size and --font-h2--size-md, --font-h2--weight, --font-h2--line-height and its -md twin, --font-h2--color, and four margin tokens — --rt-h-mt, --rt-h-mb and their -md variants. A text block writes the same shape as a --font-paragraph--* set on .rich-text__text--{{ block.id }}. A button block writes its margins on the wrapper and, on .theme-btn--{{ block.id }}, the full button token set: --rt-btn-bg, --rt-btn-text, --rt-btn-border-color, --rt-btn-border-width, --rt-btn-radius, --rt-btn-font-weight and three hover counterparts. That's 49 settings across the section and its three block types, and every one of them leaves Liquid as a custom property.

The static stylesheet, section-rich-text.css, is where the properties are consumed. The heading's font-size is calc(var(--font-heading-scale) * var(--font-h2--size)), so the theme's global heading scale still applies on top of the block's own size; the -md tokens take over at 750px inside the stylesheet's media query, which is what turns one block's two values into a responsive rule without a second set of markup.

Section-level settings add content alignment, a max-width mode — full page, or a custom pixel width that Liquid clamps against settings.page_width before emitting it as --rt-blocks-max-width in rem — a background colour, a graffiti toggle, wrapper padding for desktop and mobile, and four outer margins. When the theme's reveal-on-scroll setting is on, each block also gets Dawn's scroll-trigger animate--slide-in classes, data-cascade, and an --animation-order equal to its index, so the rewritten markup animates the way Dawn's own sections do.

highlight-text.liquid implements the [[word]] syntax. It splits the string on [[, wraps the run before ]] in a .highlight-text span and passes the rest through. The span takes its colour from --highlight-color with a fallback, so the same snippet works on a heading and on a paragraph. It opens with a {% doc %} block — @param, @example — and carries its own {% stylesheet %}. Seven other sections in the theme render it.

button-link.liquid renders <a class="theme-btn theme-btn--{{ id }}"> from a label and an href, documented the same way, with its own {% stylesheet %} consuming the button tokens against defaults. With no href it renders role="link" aria-disabled="true" instead of an empty anchor. Two other sections render it.

rich-text-graffiti.liquid layers the brand's decorative artwork: six named desktop positions and five mobile ones, absolutely positioned, aria-hidden, pointer-events: none, lazy-loaded, toggled by one section setting.

Why This Way

Emitting design tokens as custom properties on a block-id-scoped class keeps one stylesheet for every instance and pushes the variation into the cascade. That's the mechanism that lets a single section file carry three page types with three type scales: the file doesn't know which page it's on, and doesn't need to. The [[ ]] syntax gives a merchandiser one thing to learn instead of a markup language, and the setting's own info line documents it in the panel where it's typed. The custom max width is clamped in Liquid so an over-large value falls back to the page width rather than breaking the grid.

We took three costs for that, knowingly. Each block adds a dozen-odd declarations to the per-instance style block, so a long page with many blocks ships a long style block. A block's panel runs eleven to fifteen settings, which is a lot of editor for a paragraph. And the theme now enforces nothing about type consistency between pages — the tokens make any scale possible, and the design system lives in the merchandiser's habits, not the code.

Implementation Notes

  • Both snippets use the newer Liquid {% doc %} convention with @param and @example, and {% stylesheet %} blocks for snippet-scoped CSS, which the theme emits once per page however many times the snippet renders.
  • Heading, text and button blocks each expose four padding ranges — top and bottom, desktop and mobile — emitted as margin tokens rather than written inline.
  • --animation-order is emitted per block only when settings.animations_reveal_on_scroll is on, so Dawn's cascade timing still applies to the rewritten markup.
  • The custom max width is divided into rem and clamped against settings.page_width before it becomes --rt-blocks-max-width; the stylesheet's defaults are 50rem at 750px and 78rem at 990px.
  • The heading size multiplies the theme's --font-heading-scale, so the global heading-scale setting is respected rather than bypassed.
  • button-link.liquid renders role="link" aria-disabled="true" when no href is set, rather than an empty anchor.
  • The section keeps Dawn's t: locale keys for its name and block names; the new settings carry literal English labels.
  • The graffiti layer renders eleven fixed brand assets from the store's files — six desktop slots, five mobile — with the mobile and desktop sets shown by breakpoint.

Edge Cases

  • A button block with no label renders nothing, not an empty button; a button with a label and no link is exposed to assistive technology as a disabled link.
  • A custom max width greater than the theme's page width falls back to the page width.
  • Text with no [[ ]] markers passes through the highlight snippet unchanged; multiple markers each get their own span; an unclosed [[ highlights to the end of the string with the span still closed.
  • A block with no highlight colour set falls back to the snippet's default colour.
  • With reveal-on-scroll off, no animation classes or order tokens are emitted at all.

Platform Primitives Used

  • Liquid section schema and settings — 49 settings across the section and three block types, every one of them surfaced as a custom property rather than an inline rule.
  • Theme blocks — heading, text and button blocks, each with its own type, spacing and colour panel, reorderable in the editor.

Where It Runs

On the home page and on two landing-style page templates: one instance on index, three on page.about-kids and one on page.home-kids — five placements across three templates. Checked on the live storefront today: the homepage renders a heading and a button with their block-scoped token classes.

What This Demonstrates

  • Brand story and editorial section system — the primary capability: an editorial band with per-block typography, spacing and colour, which is the module a campaign page is mostly made of.
  • Design system and reusable component library — tokens emitted per instance and consumed by a static stylesheet, plus two documented snippets that own their own CSS and are rendered by other sections.

How We Know

Four Liquid files read from the theme — the section and its three snippets, roughly 930 lines between them — and the stylesheet they rely on, plus three client-side records from the redesign programme. The records establish the demand rather than the design: the working sessions settled that the static and support pages needed copy and styling changes rather than structural ones. A section that lets a merchandiser restyle a page without development is the direct answer to that. The token architecture, the snippet contracts and the highlight syntax are documented from the code.

Related Builds

The Buy-vs-Build Question

A page builder buys per-block typography in an afternoon and puts the page in the vendor's editor. Rebuilding the theme's own section bought type, spacing and colour control on Dawn's markup, in Dawn's editor, with two snippets the rest of the theme now shares — and cost a long settings panel and a design system that nothing enforces. Where that line sits is the subject of the theme section library and design system decision page.

Provenance & Evidence

  • Client: LUS Brands — loveurcurls.com
  • Surfaces: Home page, landing page
  • Templates served: three — index, page.about-kids, page.home-kids
  • Complexity: Medium — roughly 930 lines across the section and three snippets
  • Attribution: Built on top of Dawn 15.4.1; this section is our modification. rich-text.liquid keeps Dawn's file name, schema name, locale keys and its heading, text and button block types, and its body is our rewrite; highlight-text.liquid, button-link.liquid and rich-text-graffiti.liquid are ours.
  • Status: Live, verified 2026-09-07
  • Evidence: Four theme files and their stylesheet, plus three client-side records from the redesign programme, two of them working-session notes
  • Confidence: Strong — the records document the demand for restylable pages; the token architecture is documented from the code
  • Primary capability: Brand story and editorial section system

Ready to Give Your Campaign Pages a Type Scale of Their Own?

You dream it. We build it. If a campaign page still means a developer ticket for a font size, Contact us today and we'll show you what one rebuilt section can carry.

More builds