Skip to main content

Core · Show content

Prose

Sets long text in lines of about 70 characters, with space between its blocks, whatever markup an editor writes inside.

Example

A short article set as prose, with two headings, a list and a quotation.

Since March 2016, a group of volunteers has read the tide gauge in the port at six in the morning and six in the evening. Their sheets are the longest record of the sea in the bay that anyone has kept by hand, and the council will publish them as a table of open data.

How the readings were taken

Each reading is the height of the water against the gauge, to the nearest centimetre. The sheet calls it level_cm.

  • Two readings a day, every day of the year.
  • 41 volunteers over ten years.
  • Fewer than one reading in a hundred left out.

We did not set out to measure the climate. We wanted to know when to take the boats out.

Four centimetres in ten years

The average height at high tide was 4 cm higher in 2025 than in 2016, close to what the national gauges record along the coast.

Known issues

A table without a wrapper that is wider than the column pushes the page sideways on a narrow screen. Put a wide table in core's table wrapper, .luz-table-wrap, which scrolls inside itself.

Not yet tested with a screen reader.

Code

Copy the markup

HTML
<div class="luz-prose">
  <!-- What the editor or the Markdown writes, as it is -->
  <p>Since March 2016, a group of volunteers has read the tide gauge…</p>
  <h2 id="method">How the readings were taken</h2>
  <p>…</p>
  <ul class="wp-block-list">
    <li>…</li>
  </ul>
  <figure>
    <img src="gauge.jpg" width="1200" height="800" alt="The tide gauge in the port, a post marked in centimetres">
    <figcaption>The gauge at low tide.</figcaption>
  </figure>
</div>

Variants and options

There are no modifiers.

Class or propertyWhat it does
.luz-prose
On the element that holds the text: lines of about 70 characters, the space between its blocks, the indent and markers of its lists, inline code as a chip
--luz-prose-measure
The longest line, 70ch by default. Set it on the wrapper, between 60ch and 80ch

How it works

CSS only, in components/prose.css. Every selector is inside :where(), so a LUZ component inside the text, or a rule of yours, wins.

  • Line length. Each block of the wrapper is at most --luz-prose-measure wide. Figures, tables, code blocks, images, videos and .luz-table-wrap take the whole width of the wrapper, never more.
  • Space between blocks. A block starts --luz-space-md below the one before it. A heading or a rule starts further down (--luz-space-xl), and the block after a heading sits closer (--luz-space-sm). Margins, not a gap: a component inside keeps its own margin, and the two collapse.
  • Lists. Every list gets its indent and markers back, with a class or without one (an editor's ul.wp-block-list), except a list with a layout object (.o-grid, .o-stack) or a LUZ component class.
  • Inline code has the look of .luz-code-inline. Long words and addresses break rather than push the page sideways.
  • Bare elements take the same three steps between blocks in luz.css, so the two agree; prose is in a later layer, so it wins where they meet. Prose adds the line length and the lists.

Without JavaScript

Nothing changes: prose is CSS.

Keyboard and ARIA

Prose takes no focus and adds no role. Tab reaches the links and controls inside it, in source order.

  • A long <pre> scrolls sideways inside itself. To scroll it with the keyboard, give it tabindex="0", role="region" and a name, as core's code block does.
  • The headings inside make the outline of the text: start at the level under the page's title, and do not skip one.

Contrast and focus are in the Design tab.

Design

When to use

Use prose around running text whose markup you do not write block by block: the body of a post or a page from a CMS editor, Markdown, a help article.

When not to use

Do not put prose around a whole page or an interface: the header, the navigation, forms and cards lay themselves out.

  • For a column of blocks you build yourself, use .o-stack.
  • For one or two lines inside a component, such as a card's text, use the component's own classes.

Do and don't

Do Short paragraphs under headings that say what follows, so a reader can scan and find their place.
Don't One long paragraph with no heading: the reader cannot tell what it covers without reading all of it.

Writing the content

Write for someone who reads the first words of each line and skips the rest.

  • One idea a paragraph. Put the point first.
  • Headings in order, in sentence case, that say what the section is about.
  • A list for three or more things of the same kind; a sentence for two.
  • Links that say where they go, and figures with a caption that says what they show.

Contrast and focus

The text and the links keep the colours of core's elements; inline code is the code colour on a muted surface.

PartColour on backgroundLightDarkHigh contrastNeeds
Text
--luz-text-primary on --luz-surface-page
15.8:1
15.1:1
21.0:1
4.5:1
Link
--luz-brand-primary on --luz-surface-page
5.6:1
7.7:1
14.1:1
4.5:1
Quotation
--luz-text-secondary on --luz-surface-page
6.2:1
7.5:1
19.3:1
4.5:1
Inline code
--luz-color-code on --luz-surface-muted
5.3:1
8.1:1
9.0:1
4.5:1
Focus ring
--luz-focus-ring on --luz-surface-page
5.6:1
7.7:1
21.0:1
3:1

Measured from the token values of each mode, rounded down. Needs is the WCAG 2.2 AA minimum: 4.5:1 for text, 3:1 for focus rings, borders and icons.

  • Focus: links keep core's 2 px --luz-focus-ring outline, 2 px outside the text.
  • Links: underlined, so they are not marked by colour alone (WCAG 1.4.1).

Tokens it uses

The 11 tokens that components/prose.css reads, by family. Each family links to its page, with the values in every mode. In Figma they are variables with the same names: see Tokens in Figma.

Spacing and sizing
--luz-space-2xs, --luz-space-3xs, --luz-space-md, --luz-space-sm, --luz-space-xl, --luz-space-xs
Typography
--luz-text-xs
Radius
--luz-radius-sm

Keyboard and ARIA are in the Code tab.