Skip to content

Conventions

These are the rules every developer and content editor working in this theme needs to follow. Breaking them causes visual bugs, accessibility failures, or build errors that are hard to trace.

All colours come from the @theme block in frontend/entrypoints/css/theme.css. No arbitrary hex values in class names — ever.

Palette:

  • Neutrals — white, salt, warm-gray, off-white
  • Charcoal scale — charcoal-50 through charcoal-600, charcoal (base), light-charcoal
  • Kale (brand green) — kale-200, kale-300, kale-400, kale (base)
  • Accents — green, blue, berry, tan

Two brand font families:

  • TT Rationalist (serif) — headings, quotes, body-2xl. Weights: 200, 300.
  • Restart Soft (sans) — body text, buttons, smaller headings. Weights: 300, 400.

Typography is applied through utility classes like text-heading-lg or text-body-md, which set font-family, size, line-height, letter-spacing, and weight in one class. Never set font-family directly — use the utility.

Sizes cascade upward: mobile is the base. Tablet overrides at 768px, desktop at 1280px. Leave tablet and desktop on “Default” and the mobile size applies everywhere.

Sections use a six-setting padding pattern: top and bottom for mobile, tablet, and desktop. Values are in pixels with step 2 or 4.

class="pt-[var(--padding-top)] md:pt-[var(--padding-top-tablet)] lg:pt-[var(--padding-top-desktop)]
pb-[var(--padding-bottom)] md:pb-[var(--padding-bottom-tablet)] lg:pb-[var(--padding-bottom-desktop)]"
style="
--padding-top: {{ s.padding_top_mobile }}px;
--padding-bottom: {{ s.padding_bottom_mobile }}px;
--padding-top-tablet: {{ s.padding_top_tablet }}px;
--padding-bottom-tablet: {{ s.padding_bottom_tablet }}px;
--padding-top-desktop: {{ s.padding_top }}px;
--padding-bottom-desktop: {{ s.padding_bottom }}px;
"

The three breakpoints are independent — changing desktop does nothing to mobile.

Every section follows the same pattern:

  1. Static blocks for pinned header content (title, tag, richtext) — {% content_for 'block', type: 'title', id: 'section-title' %}
  2. Theme blocks for repeatable content — {% content_for 'blocks' %}
  3. Each block renders itself. The section never reads block settings.

Block files live in blocks/ and are never redefined per section. Use {% content_for 'block' %} for static blocks and {% content_for 'blocks' %} for the reorderable collection. Never {% render block %}.

Every schema setting needs a translation in locales/en.default.schema.json.

  • Blocks (blocks/*.liquid) — translations go in the "blocks" section
  • Sections (sections/*.liquid) — translations go in the "sections" section
  • Mixing them up causes errors

Always search for an existing key before adding one — duplicates cause errors.

No ternary operators. Liquid doesn’t support condition ? true : false. Use if/elsif/else/endif.

Multi-line filters work in individual tags ({% assign %}) but not inside {% liquid %} blocks. In liquid blocks, keep filter chains on one line.

Richtext defaults must have valid HTML top-level tags (<p>, <h1>–<h6>, <ul>, <ol>). Plain text without tags fails schema validation.

CSS-first configuration — no tailwind.config.js. All tokens live in frontend/entrypoints/css/theme.css.

/* Use width queries, not @screen */
@media (width >= 1024px) { ... } /* correct */
@screen lg { ... } /* not supported */
/* Use CSS variables, not theme() */
color: var(--color-kale); /* correct */
color: theme("colors.kale"); /* not supported */

CSS files using @apply must include @reference directives:

@reference "tailwindcss";
@reference "../theme.css";

Every section schema must have section_id as its first setting — no exceptions.

Collection pages can be composed from hand-picked rows instead of the auto-fill grid. When filters are applied, the curated rows hide and a standard grid takes over. See Curated Collection Templates for the full pattern.