Part 1

Structure

Bootstrap style page structure, built in Tailwind. Four layers, always in this order.

The four structural layers, class and job
Layer Class Job
Section <section> One block of the page. Sets vertical rhythm (py-16 marketing, py-8 product).
Container .container Centres content, caps width at 1220px, adds side padding.
Row .row Flexbox, not CSS Grid. Wraps, negative margin lines up columns, gap-y-8 spaces stacked columns on mobile.
Column .col Paired with a Tailwind width, e.g. md:w-1/2.
<section>
  <div class="container">
    <div class="row">
      <div class="col md:w-1/2">...</div>
      <div class="col md:w-1/2">...</div>
    </div>
  </div>
</section>
  • Never nest .container inside .container. One per section.
  • A row of columns always needs .row as the wrapper, even for one column. .col depends on the row's negative margin and gap.
  • Not Bootstrap's own classes, no .col-md-6. Widths are plain Tailwind fractions (w-1/2, w-1/3, w-1/4...), but they are still built on a 12-column base, so columns of different widths line up across rows.
  • CSS Grid is not used anywhere on the site.

Part 2

Foundations

Marketing and product tracks

Marketing pages (Belong, Placid, case studies, principles) and product pages (Spoke, Bearing) are styled differently, on purpose.

Marketing track versus product track, by design property
Marketing Product
Section rhythm py-16 py-8
H1 font Mochiy Pop One (font-serif, default) Bitter (font-sans override)
H2 size text-3xl text-xl
Corner radius rounded-2xl rounded-md
Focus states Global outline .input-focus-ring on every field

Marketing

Inspiration for inclusion

Generous space, a display headline, primary colour throughout.

Product

4.1 Local authorities must publish a Local Offer

Dense rhythm, functional heading, colour saved for status and actions.

Typography

Every bare <h1> to <h6> gets text-wrap: balance automatically. No orphan word on a heading's last line.

Marketing and product share the same scale. H1 font and H2 size differ by track, see Marketing and product tracks above. H3 and the named classes below are the same on both.

This is what an h1 looks like

font-serif text-3xl/snug lg:text-4xl/snug. Base rule for a bare h1, marketing pages need no class. Product overrides with font-sans, see the tracks table above. One h1 per page.

This is a real h2

Marketing size shown here. Product uses text-xl instead.

This is a real h3

A card or content subheading. Smaller and more frequent than a section's one h2.

A section-title heading

.section-title. A section's one major heading, marketing size only.

Belong

.eyebrow. Small bold label above a hero, naming what the thing is. Skip it if the h1 already says it.

Together, today and tomorrow

.hero-tagline. The line between .eyebrow and a hero's h1.

Photo by Jonny Holden

.photo-credit. Under a hero image, base size like everything else.

Nothing below 16px

  • No text anywhere under the 16px base. None of Tailwind's smaller text-size utilities (the ones a size code like "sm" or "xs" would pick), no stylesheet size under 1rem.
  • Hints, captions, tags, table cells, photo credits: all base size. Secondary feel comes from colour and weight, not size.
  • Check: grep -o 'text-\(sm\|xs\)\b' site/templates/*.php should print nothing.

This is body text at the base size.

This is secondary text. Same size, quieter colour.

text-dark-600 for secondary text, not a smaller size

Inclusion Draft

.tag and .tag-neutral at base size too

Colour

  • Full scales live in tailwind.config.js.
  • dark-200 is the card border colour. Always paired with a shadow, since border alone is only about 1.24 to 1 contrast.
  • Info, success, warning and danger are each checked at 4.5 to 1 contrast or better against white. Full rationale on the wiki.

Primary

50

#fdf4f8

100

#fedfe8

200

#fbbace

300

#f598b7

400

#ee76a2

500

#e6528e

600

#dd227c

700

#c30067

800

#a50050

900

#870039

950

#680023

Secondary

50

#fdf2f8

100

#fce7f3

200

#fbcfe8

300

#f9a8d4

400

#f472b6

500

#ec4899

600

#db2777

700

#be185d

800

#9d174d

900

#831843

950

#500724

Tertiary

50

#f5f3ff

100

#ede9fe

200

#ddd6fe

300

#c4b5fd

400

#a78bfa

500

#8b5cf6

600

#7c3aed

700

#6d28d9

800

#5b21b6

900

#4c1d95

950

#2e1065

Dark

50

#f9fafb

100

#f3f4f6

200

#e5e7eb

300

#d1d5db

400

#9ca3af

500

#6b7280

600

#4b5563

700

#374151

800

#1f2937

900

#111827

950

#030712

Light

50

#ffffff

100

#f3f4f6

200

#e5e7eb

300

#d1d5db

Neutral

50

#f5f5f5

100

#e9e9e9

200

#d0d0d0

300

#b7b7b7

400

#9f9f9f

500

#858585

600

#646464

700

#464646

800

#3a3a3a

900

#2f2f2f

950

#232323

Info

50

#f2f4f8

100

#dee4f2

200

#b1c2e7

300

#7394de

400

#4d7be0

500

#376ee6

600

#2563eb

700

#1147bd

800

#0e3a9a

900

#0b2f7d

950

#092564

Success

50

#f2f8f2

100

#e1f2de

200

#b9e7b1

300

#81de73

400

#48bb37

500

#379e28

600

#2b861d

700

#216616

800

#1b5312

900

#16430f

950

#11360c

Warning

50

#f8f4f2

100

#f2e7de

200

#e7cab1

300

#dea373

400

#e17821

500

#cc6712

600

#b8590a

700

#8c4408

800

#723706

900

#5c2d05

950

#4a2404

Danger

50

#f8f2f2

100

#f2dede

200

#e7b1b1

300

#de7373

400

#d55050

500

#d83a3a

600

#dc2626

700

#a91b1b

800

#8a1616

900

#6f1212

950

#590e0e

Link

Default link colour is Primary (shown above), from the global a rule. Every link is coloured and underlined, everywhere (see Design principles).

A separate flat colour, text-link, exists only for one narrow case: a course lesson list's not-yet-done links (done ones use Tertiary instead).

link

#1d4ed8

Blob characters

  • Our illustrations. The drawn share cards use them too.
  • In the Image Library, tagged blob. Use Choose from library to put one in any image field.
  • Add, remove or re-describe them in the Brand images field on this page. This section follows.
  • The words under each one are its image description, which screen readers read out.
  • Round pink blob with a quiff, arms out, smiling
  • Pink blob lying on its front, watching a video on a tablet
  • Pale blob with pigtails holding a phone, a speech bubble of coloured circles
  • Tall pink blob with dark curly hair, standing shyly
  • Grey blob in headphones reading a book, sound waves in a speech bubble
  • Grey blob with a white cane, walking with a guide dog
  • Tall pale blob with long hair, typing on a laptop at a standing desk
  • Magenta blob in glasses with a hand on its head, looking puzzled
  • Pink blob with curly hair and glasses, holding a tablet showing a music note
  • Grey blob at a desk, changing text size and settings on a computer screen
  • Pale blob holding a birthday cake with a number 10 candle
  • Pink blob in a wheelchair, making a pot on a potter's wheel

Part 3

Components

Cards

Border plus shadow define the edge. Border colour alone is not enough contrast on its own. Radius: rounded-md product, rounded-2xl marketing (shown here).

Pick the pattern by what the box does, not by looks: is the whole thing one link, does it hold exactly one button, or is there nothing to click?

Accessible

One of the five Local Offer principles.

Whole card is one link. .card plus .stretched-link on the heading. Never wrap the card itself in an <a>.

The vision

One emphasised item, not several. Tinted fill, no border.

See the vision

Exactly one button inside. Card is not a link, only the button is, sized to its own text.

153 councils

A stat, an aside, a category explanation. Nothing in it links anywhere.

Nothing to click. .panel, not .card. Flat fill, no shadow, no border. The moment it needs a link, make it a card.

A button inside a flex-column card needs its own wrapper (a <p>), or it stretches full width.

Short version

One line of text.

Read more

Long version

The same card, with a summary running to two or three lines instead of one.

Read more

Belong The Universal Offer Agreement

A coloured eyebrow bar over a tinted body, one heading with the eyebrow label folded in.

.card-feature. Home page product intros only. Rare, not a general option.

Testimonial speech bubbles

  • Six hand-drawn illustrations, not a CSS shape. Used on Belong and Spoke's marketing page.
  • Auto-rotates through the six by list position, or an editor can pin a shape and nudge the text.

"So much easier than the old website."

Oval, pink fill

"So much easier than the old website."

Oval, outline only

"So much easier than the old website."

Rectangle

"So much easier than the old website."

Rounded square

"So much easier than the old website."

Starburst

"So much easier than the old website."

Thought cloud

testimonialBubbleShapes() / resolveTestimonialBubble() in _init.php.

Blockquote

For a quote inside flowing rich text: a newsletter pull quote, an interview excerpt, a Code of Practice citation. For a real standalone testimonial, use the speech bubbles above instead.

The new Local Offer is so much easier to use, and we don't get lost in the council's own website like we used to.

Calderdale parent carer
  • Tinted card, primary-600 left border, no decorative quote mark.
  • Use a real <cite> for the attribution, not a second plain paragraph.
  • No attribution is fine. Most Code of Practice citations have none.

Post-it notes

A torn or taped sticky-note illustration as a card's background image, one per SEND Local Offer principle. hero_bg_image on the "principle" template.

Real per-page art, not a reusable class, so it is not reproduced here. See it live: Accessible. On a page showing several, do not wrap it in .card. Keep rounded corners and a plain shadow, drop the border.

Buttons, tags, and CTA banners

One .btn-primary per group, the rest .btn-outline. Never two solid buttons in one group. Two outline buttons is fine when neither is the default.

Inverse variant is for a dark background only, not a second way to mark a secondary action.

Inclusion SEND funding Conference Webinar

.tag for topics, .tag-neutral for classification. One recipe each.

Book a demo of Belong

See how Belong can help SENCOs, teachers, and TAs

Book a demo

One radius, rounded-2xl, always.

Focus states

Product track inputs get a visible ring on top of the global outline. Tab into the field below.

Same ring on .card/.card-feature at :focus-within, and on Spoke's .status-pill/.ghost-field.

Status icons

Border plus symbol, not a filled background, so a status stays quiet until it matters. CC0 icons from SVG Repo, kept in site/templates/icons/.

Success
Warning
Danger
Info

Main use: a form field that has failed validation

Enter a full web address, starting with https://

Product icons

One icon per product or page, matched to its name, not a generic glyph. productIcon($name) / productIconName($name) in _init.php. Every distinct icon the map resolves to, one label per icon (several keys can share one icon, e.g. "bearing" and "bearings").

Belong
Placid
Spoke
Bearing
Dawn
Open SEND data
Every
Library
Ask
Survey
Chorus
Help
Support
Contact
Learn
Readability course
Accessible social course
HTML and Markdown course
Workspace
Newsletter

w-4 h-4 in the nav dropdown, w-5 h-5 white on a .card-feature-bar.

Inline icon codes

Every icon in site/templates/icons/ with the code that puts it inline in rich text. Copy the code, paste it into the editor where the icon should go. Works today in the AI search walkthrough's cover page intro and step screens (aiSearchInlineIcons()); it renders sized to the text, in the accent colour.

  • {{ICON:ai}}
  • {{ICON:bearing}}
  • {{ICON:belong}}
  • {{ICON:blog}}
  • {{ICON:book-open}}
  • {{ICON:book}}
  • {{ICON:briefcase}}
  • {{ICON:bullseye-cursor}}
  • {{ICON:chevron-down}}
  • {{ICON:clipboard-list-check}}
  • {{ICON:code}}
  • {{ICON:comment-ellipsis}}
  • {{ICON:copy}}
  • {{ICON:danger}}
  • {{ICON:dawn}}
  • {{ICON:envelope-open}}
  • {{ICON:envelope}}
  • {{ICON:everysend}}
  • {{ICON:external-link}}
  • {{ICON:file}}
  • {{ICON:gear}}
  • {{ICON:info}}
  • {{ICON:library}}
  • {{ICON:life-ring}}
  • {{ICON:link}}
  • {{ICON:local-authority}}
  • {{ICON:location}}
  • {{ICON:mail}}
  • {{ICON:map}}
  • {{ICON:message-ellipsis}}
  • {{ICON:message-lines}}
  • {{ICON:microphone}}
  • {{ICON:mobile-heart}}
  • {{ICON:opensenddata}}
  • {{ICON:phone}}
  • {{ICON:placid}}
  • {{ICON:post-it}}
  • {{ICON:quote}}
  • {{ICON:rocket}}
  • {{ICON:search}}
  • {{ICON:spoke}}
  • {{ICON:success}}
  • {{ICON:target}}
  • {{ICON:user}}
  • {{ICON:users}}
  • {{ICON:video-camera}}
  • {{ICON:warning}}
  • {{ICON:workspace}}

Native popover attribute, not <details>. No JavaScript for open, close, escape, click outside or focus return. Try the real thing below.

Positioned with CSS anchor positioning. Chevron rotates via :has(). One feature-detected polyfill script, skips itself where the browser already supports it.

Spoke's item table

Compact row per checklist item, not one big card each. Loud .status-pill for status, near-invisible .ghost-field for responsibility and notes.

4.1 Local authorities must publish a Local Offer

A real <details>/<summary> holding a real <h4>. Screen reader users can navigate up to 57 items in a section by heading, not by tabbing one at a time.

Every real status label, auditStatusPillClass($status) in _init.php

Live Weak Review Started Policy Strategy Settings To Do Nothing

.status-pill-static for read-only display (Spoke's Views/Gaps pages). Same shape and colour, no dropdown chevron, since there is nothing to click. 9 labels, 5 real colours. Policy, Strategy and Settings share tertiary. To Do and Nothing share dark.

Details, folded content

The general form of the table row above: a native <details>/<summary>, class .disclosure, for content that is secondary and most people will not need. Never for the main task, and never for something the page is incomplete without. Keyboard, screen reader (announced expanded or collapsed) and find in page (the browser opens a closed one when the search matches text inside) all come free, with no JavaScript.

Rules: the summary holds a real heading at the right level for the page, so it can be reached by heading. Native marker hidden, a + or – at the right end instead. Closed by default. Body content in .disclosure-body. Several in a row is fine for a few folded extras; a set of sections the reader works through is a different thing, and not this. Seen live under the Start button on the AI search walkthrough.

Why is the border only at the bottom?

The container carries the top border, each item its bottom one, so a stack of them reads as one list of folds rather than a pile of boxes.

Can the summary hold an icon?

Yes, inside the heading, before the text, sized to it. The walkthrough’s cover page uses the AI and clipboard icons that way.

Part 4

Rich text (.field-html)

Shared .field-html h2/h3/h4 rules set weight, size and margin. Weight is global (font-weight: 600), the same on every heading level everywhere, no class needed. Size and margin genuinely vary by content shape, so those stay per-template.

Check this table before writing a new template. A size nobody styled for falls back to the global default (1.5rem h2, 1.25rem h3). That default is a middle ground: too small for a standalone article's own section heading, too large for a compact sidebar card. Pick the row that matches the content's shape instead of trusting the fallback.

Which .field-html heading recipe to use, by content shape
Content shape Recipe Example templates
Whole article, own heading may lead the field [&_h2]:text-2xl ... [&_h3]:text-lg ... procurement-document.php, newsletter-edition.php, blog-post.php
h3 only, section's real h2 sits outside .field-html [&_h3]:text-lg ... case-study-detail.php, engagement-framework.php
Single h2, no lower level, the block's one title [&_h2]:text-{lg|xl|2xl|3xl} ... belong.php (text-3xl), project-planning-tool.php sidebar (text-lg)

Everything else is safe globally, no per-template opt-in

  • Heading weight, as above.
  • Nested lists: a sub-list does not add its own trailing gap.
  • <dl>/<dt>/<dd>: bold term, indented definition.
  • <a>: one global rule, no per-template gap possible.
  • <code>: Tailwind's monospace stack, free.

Part 5

Motion and focus

  • prefers-reduced-motion: one media query cuts every animation and transition to near zero, automatically, everywhere.
  • :focus-visible: one base rule gives every element a visible outline. The two places that remove it replace it with a ring at least as visible.

Part 6

Design principles

  1. 1

    Never use full capitalisation, anywhere

    Not in an eyebrow bar, a tag, or a badge. Always sentence case.

  2. 2

    Every link is coloured and underlined, everywhere

    Colour alone fails colourblind users. A button or card style (.stretched-link, .btn) may drop the underline. Plain inline text links never do.

  3. 3

    Section rules apply to one major heading and the section's own padding

    Not to card titles or one-off inner spacing. Those are a smaller, different role.

  4. 4

    Shared components, per-track overrides

    .card and .card-tint are shared. Marketing's rounded-2xl is a per-use override, not a change to the default.

  5. 5

    A card's title needs a real heading element

    Not a styled span. Screen readers navigating by heading skip a span standing in for one.

Full detail on the Design system wiki page.

Part 7

Product track status

Spoke and Bearing use the product track above. Spoke's own pitch page, spoke-marketing.php, is a separate template, deliberately marketing track.