Component
prose
Demo
tokens.css,
base.css and prose.css. No scripts.
Source
Copy both files, or run npx nojsui add prose.
Browser support
Works across current and earlier versions of every major engine.
| Feature | Baseline | Chrome | Edge | Firefox | Safari | Chrome Android | Firefox Android | Safari iOS |
|---|
Usage
<div class="sk-prose">
<p class="sk-prose__eyebrow">What we do</p>
<h2 class="sk-prose__heading">Interfaces that get out of the way</h2>
<p class="sk-prose__lede">The one-sentence version, set larger and quieter.</p>
<p>Body copy, at a reading measure.</p>
</div>
The eyebrow / heading / lede triplet is the pattern every marketing section on earth repeats, and it is hand-written every time — three elements whose only real content is the space between them. That spacing is the component.
The heading is a class, not an element
.sk-prose__heading styles the class, so it works on <h1>, <h2> or <h3>.
Heading level is a document structure decision — it depends on what is above
this block on the page — and it is not the same decision as how large the text
should be. Tying the two together is how pages end up with an <h3> chosen for
its font size.
Body copy is unclassed
p and ul inside .sk-prose are styled only when they carry no class:
.sk-prose p:not([class]),
.sk-prose ul:not([class]) { … }
The :not([class]) is load-bearing. .sk-prose__lede is a <p>, and a plain
.sk-prose p rule has higher specificity than .sk-prose__lede (0-1-1 against
0-1-0), so it would win and the lede would lose its own spacing. Excluding
classed elements means any part you name keeps the styling you gave it, and
everything you did not name gets the default rhythm.
Theming
| Property | Default | Controls |
|---|---|---|
--sk-prose-measure | --sk-size-measure-reading | Line length for the whole block |
--sk-prose-eyebrow-fg | --sk-color-accent | Eyebrow colour |
--sk-prose-heading-size | --sk-text-3xl | Heading size |
--sk-prose-lede-fg | --sk-color-text-muted | Lede colour |
--sk-prose-link-fg | --sk-color-accent | Inline link colour |
Keyboard contract
None — it is text. Links inside keep their native behaviour and the focus ring
base.css draws.
Accessibility notes
- The eyebrow is
text-transform: uppercase, which changes the rendering only. Screen readers announce the underlying text, so write it in sentence case in the markup — “What we do”, not “WHAT WE DO”. Some screen readers spell out short all-caps strings letter by letter. - The eyebrow is a
<p>, not a heading. It reads as a label for the heading below it, and promoting it to<h3>above an<h2>would put the document outline in the wrong order for a purely visual effect. --sk-prose-eyebrow-fgdefaults to the accent colour, whichtools/tokens/check-contrast.mjsverifies against both surfaces.- The measure is in
ch, so it follows the reader’s typeface and text size instead of fighting them.
Degradation
Baseline column from support.json — regenerate with pnpm support.
| Feature used | Baseline status | Behavior without it |
|---|---|---|
| Logical properties | widely | Nothing to degrade — Baseline widely available since 2021. |
base.css adds text-wrap: balance to headings and text-wrap: pretty to
paragraphs where supported, which is what keeps a heading from breaking with
one word on the last line. Without it the text sets normally.