Prose

The .prose class is meant for long-form content styling of bare HTML, such as that produced by Markdown processors.

Examples

Intent

Brio in plain HTML

A small CSS library is mostly an opinionated stylesheet over semantic markup. The .prose class is the part that handles long-form content, the kind of writing where you'd otherwise reach for a CMS theme or a typography reset.

Why prose

Most pages in a docs site or knowledge base aren't single components. They're paragraphs, headings, code blocks, lists, occasional images, the rare blockquote. Each could be wired up with its own class, but the volume makes that tedious. Bare HTML inside a single .prose wrapper is faster to write, faster to read, and survives copy-paste from external sources.

Lists

Unordered

  • Sets typography for headings, paragraphs, links, lists, blockquotes.
  • Styles tables and definition lists when they aren't classed.
  • Adds visual chrome to <details>, <kbd>, <abbr>, <mark>, and friends.
  • Survives nesting:
    • Indents naturally.
    • Uses muted markers.
    • And nests further:
      • Three levels deep stays readable.
      • Each level inherits the rhythm.

Ordered

  1. Author the markup.
  2. Wrap in .prose.
  3. Ship.

Task lists

  • Port v0.1 components to brio.
  • Add the v0.2 extras layer.
  • Write a real consumer site.
  • Retire the transfer manifest.

Blockquotes

The best way to predict the future is to invent it. Hype-driven front-end churn invents it for us, in the worst possible direction.

A small library you fully understand beats a large one you rent.

— Brio, internal philosophy notes

Inline elements

Inline code is set in --font-mono with a quiet --color-fill-subtle background, so references like --prose-margin-block or document.querySelector() read distinctly without yelling.

Press Cmd + K to open the command palette. The Esc key dismisses it.

Abbreviations get a dotted underline and a help-cursor: CSS handles the visual layer, HTML carries the structure, and JS only enters where the platform falls short.

Use highlighted text sparingly -- it's loud enough that one or two phrases per article register; more, and the eye stops noticing.

Strikethrough text comes in two flavors: deleted content for editorial removal and obsolete syntax for things the language used to accept.

Tables

Bare tables get a quiet treatment -- no outer border, just header underline and row dividers. Use a classed <table class="table"> for the full component (with hover, striping, density, etc.).

Token Default Purpose
--prose-margin-block --space-sm-md Block-axis margin between direct children.
--prose-max-width 65ch Readable line length for long-form content.
--prose-blockquote-border-color --color-border-strong Inline-start accent on blockquotes.
--prose-blockquote-border-width 3px Width of the blockquote accent.

Definition lists

Atom
A small, self-contained component. .button, .input, .badge.
Composition primitive
Layout helper that arranges children. .cluster, .stack, .media.
Slotted container
A root with named child slots. .tabs, .timeline, .stat.

Disclosure

What's inside a bare <details>?

Prose adds a quiet border, padding, and a custom rotating chevron via ::before on the summary. The expand-collapse animation comes from brio's global details.css (which applies to every <details>); prose only adds the visual chrome.

Reach for <details class="details"> when you want brio's full component (richer chrome, token-driven theming, hover states).

Images and figures

Abstract textured rectangle, illustrative placeholder
A figure pairs an image with a caption. Prose styles the caption smaller and muted so it reads as supporting text.

Footnotes and rules

Inline references can point to numbered footnotes 1 at the end of the article. Goldmark and other GFM-flavor markdown processors emit a .footnotes section automatically.


Horizontal rules make natural breakpoints in long articles -- shifting topics, separating an aside from the main flow, marking the end of the body before footnotes. Prose gives them a quiet 1px line and generous block-margins.


  1. Footnotes use .footnotes as the section wrapper. Prose drops the font size a step so the metadata sits below the body without competing. ↩
<article class="prose">
            <h1>Brio in plain HTML</h1>

            <p>
                A small CSS library is mostly an opinionated stylesheet over semantic markup. The
                <code>.prose</code> class is the part that handles long-form content, the kind of
                writing where you'd otherwise reach for a CMS theme or a typography reset.
            </p>

            <h2>Why prose</h2>

            <p>
                Most pages in a docs site or knowledge base aren't single components. They're
                paragraphs, headings, code blocks, lists, occasional images, the rare blockquote.
                Each could be wired up with its own class, but the volume makes that tedious. Bare
                HTML inside a single <code>.prose</code> wrapper is faster to write, faster to read,
                and survives copy-paste from external sources.
            </p>

            <h2>Lists</h2>

            <h3>Unordered</h3>

            <ul>
                <li>Sets typography for headings, paragraphs, links, lists, blockquotes.</li>
                <li>Styles tables and definition lists when they aren't classed.</li>
                <li>
                    Adds visual chrome to <code>&lt;details&gt;</code>, <code>&lt;kbd&gt;</code>,
                    <code>&lt;abbr&gt;</code>, <code>&lt;mark&gt;</code>, and friends.
                </li>
                <li>
                    Survives nesting:
                    <ul>
                        <li>Indents naturally.</li>
                        <li>Uses muted markers.</li>
                        <li>
                            And nests further:
                            <ul>
                                <li>Three levels deep stays readable.</li>
                                <li>Each level inherits the rhythm.</li>
                            </ul>
                        </li>
                    </ul>
                </li>
            </ul>

            <h3>Ordered</h3>

            <ol>
                <li>Author the markup.</li>
                <li>Wrap in <code>.prose</code>.</li>
                <li>Ship.</li>
            </ol>

            <h3>Task lists</h3>

            <ul>
                <li><input type="checkbox" checked /> Port v0.1 components to brio.</li>
                <li><input type="checkbox" checked /> Add the v0.2 extras layer.</li>
                <li><input type="checkbox" /> Write a real consumer site.</li>
                <li><input type="checkbox" /> Retire the transfer manifest.</li>
            </ul>

            <h2>Blockquotes</h2>

            <blockquote>
                <p>
                    The best way to predict the future is to invent it. Hype-driven front-end churn
                    invents it for us, in the worst possible direction.
                </p>
                <p>A small library you fully understand beats a large one you rent.</p>
                <cite>— Brio, internal philosophy notes</cite>
            </blockquote>

            <h2>Inline elements</h2>

            <p>
                Inline code is set in
                <code>--font-mono</code> with a quiet <code>--color-fill-subtle</code> background,
                so references like <code>--prose-margin-block</code> or
                <code>document.querySelector()</code> read distinctly without yelling.
            </p>

            <p>
                Press <kbd>Cmd</kbd> + <kbd>K</kbd> to open the command palette. The
                <kbd>Esc</kbd> key dismisses it.
            </p>

            <p>
                Abbreviations get a dotted underline and a help-cursor:
                <abbr title="Cascading Style Sheets">CSS</abbr> handles the visual layer,
                <abbr title="HyperText Markup Language">HTML</abbr> carries the structure, and
                <abbr title="JavaScript">JS</abbr>
                only enters where the platform falls short.
            </p>

            <p>
                Use <mark>highlighted text</mark> sparingly -- it's loud enough that one or two
                phrases per article register; more, and the eye stops noticing.
            </p>

            <p>
                Strikethrough text comes in two flavors:
                <del>deleted content</del> for editorial removal and <s>obsolete syntax</s> for
                things the language used to accept.
            </p>

            <h2>Tables</h2>

            <p>
                Bare tables get a quiet treatment -- no outer border, just header underline and row
                dividers. Use a classed
                <code>&lt;table class="table"&gt;</code> for the full component (with hover,
                striping, density, etc.).
            </p>

            <table>
                <thead>
                    <tr>
                        <th>Token</th>
                        <th>Default</th>
                        <th>Purpose</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td><code>--prose-margin-block</code></td>
                        <td><code>--space-sm-md</code></td>
                        <td>Block-axis margin between direct children.</td>
                    </tr>
                    <tr>
                        <td><code>--prose-max-width</code></td>
                        <td><code>65ch</code></td>
                        <td>Readable line length for long-form content.</td>
                    </tr>
                    <tr>
                        <td><code>--prose-blockquote-border-color</code></td>
                        <td><code>--color-border-strong</code></td>
                        <td>Inline-start accent on blockquotes.</td>
                    </tr>
                    <tr>
                        <td><code>--prose-blockquote-border-width</code></td>
                        <td><code>3px</code></td>
                        <td>Width of the blockquote accent.</td>
                    </tr>
                </tbody>
            </table>

            <h2>Definition lists</h2>

            <dl>
                <dt>Atom</dt>
                <dd>
                    A small, self-contained component. <code>.button</code>, <code>.input</code>,
                    <code>.badge</code>.
                </dd>
                <dt>Composition primitive</dt>
                <dd>
                    Layout helper that arranges children. <code>.cluster</code>,
                    <code>.stack</code>, <code>.media</code>.
                </dd>
                <dt>Slotted container</dt>
                <dd>
                    A root with named child slots. <code>.tabs</code>, <code>.timeline</code>,
                    <code>.stat</code>.
                </dd>
            </dl>

            <h2>Disclosure</h2>

            <details>
                <summary>What's inside a bare <code>&lt;details&gt;</code>?</summary>
                <p>
                    Prose adds a quiet border, padding, and a custom rotating chevron via
                    <code>::before</code> on the summary. The expand-collapse animation comes from
                    brio's global <code>details.css</code> (which applies to every
                    <code>&lt;details&gt;</code>); prose only adds the visual chrome.
                </p>
                <p>
                    Reach for <code>&lt;details class="details"&gt;</code>
                    when you want brio's full component (richer chrome, token-driven theming, hover
                    states).
                </p>
            </details>

            <h2>Images and figures</h2>

            <figure>
                <img
                    src="https://picsum.photos/seed/prose-fig/800/360"
                    alt="Abstract textured rectangle, illustrative placeholder"
                />
                <figcaption>
                    A figure pairs an image with a caption. Prose styles the caption smaller and
                    muted so it reads as supporting text.
                </figcaption>
            </figure>

            <h2>Footnotes and rules</h2>

            <p>
                Inline references can point to numbered footnotes
                <sup><a href="#fn-1">1</a></sup> at the end of the article. Goldmark and other
                GFM-flavor markdown processors emit a <code>.footnotes</code> section automatically.
            </p>

            <hr />

            <p>
                Horizontal rules make natural breakpoints in long articles -- shifting topics,
                separating an aside from the main flow, marking the end of the body before
                footnotes. Prose gives them a quiet 1px line and generous block-margins.
            </p>

            <section class="footnotes">
                <hr />
                <ol>
                    <li id="fn-1">
                        Footnotes use <code>.footnotes</code> as the section wrapper. Prose drops
                        the font size a step so the metadata sits below the body without competing.
                        <a href="#" aria-label="Back to reference">↩</a>
                    </li>
                </ol>
            </section>
        </article>

Mixing prose with brio components

Inside a prose block

Sometimes you need to use styled elements, like <code> or <table> inside a prose container. For that, you can drop a classed element inside and it should work as expected, with it's original class-based styling.

Class Wins
<table class="table"> Brio's table component (this row).
<table> Prose's quiet treatment (the table above).

Same rule for unordered lists. Aa classed <ul class="cluster"> stays a horizontal cluster instead of a vertical disc list.

  • Composition wins
  • Even inside prose
  • No specificity conflict

Code blocks pick up brio's code component, including header, syntax tokens, and copy button, when they have the class:

button.css
export const version = "0.1.0";
<article class="prose">
            <h2>Inside a prose block</h2>

            <p>
                Sometimes you need to use styled elements, like <code>&lt;code&gt;</code> or
                <code>&lt;table&gt;</code>
                inside a prose container. For that, you can drop a classed element inside and it
                should work as expected, with it's original class-based styling.
            </p>

            <table class="table is-striped is-bordered">
                <thead>
                    <tr>
                        <th>Class</th>
                        <th>Wins</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td><code>&lt;table class="table"&gt;</code></td>
                        <td>Brio's table component (this row).</td>
                    </tr>
                    <tr>
                        <td><code>&lt;table&gt;</code></td>
                        <td>Prose's quiet treatment (the table above).</td>
                    </tr>
                </tbody>
            </table>

            <p>
                Same rule for unordered lists. Aa classed
                <code>&lt;ul class="cluster"&gt;</code> stays a horizontal cluster instead of a
                vertical disc list.
            </p>

            <ul class="cluster">
                <li><span class="badge is-success">Composition wins</span></li>
                <li><span class="badge is-accent">Even inside prose</span></li>
                <li><span class="badge is-warning">No specificity conflict</span></li>
            </ul>

            <p>
                Code blocks pick up brio's
                <a href="{{.Site.BasePath}}/components/code">code component</a>, including header,
                syntax tokens, and copy button, when they have the class:
            </p>

            <div>
                <div class="code-header is-accent">button.css</div>
                <pre class="code"><code>export const version = "0.1.0";</code></pre>
            </div>
        </article>

.is-wide

Wide prose

The .is-wide variant inherits all the same prose styling, but it doesn't clamp the inline-size values. It's useful on pages where the content has its own width or where the normal prose width isn't appropriate.

<article class="prose is-wide">
            <h3>Wide prose</h3>
            <p>
                The <code>.is-wide</code> variant inherits all the same prose styling, but it
                doesn't clamp the <code>inline-size</code> values. It's useful on pages where the
                content has its own width or where the normal prose width isn't appropriate.
            </p>
        </article>

Source comments

Prose -- long-form content styling.

Styles every standard HTML element for prose contexts via descendant
selectors: headings, paragraphs, lists (ordered, unordered, GFM task
lists, nested), blockquotes, inline code, horizontal rules, figures,
unclassed tables, definition lists, <kbd>, <abbr>, <mark>, <del>/<s>,
superscript links, and GFM footnotes.

Works with raw HTML -- no child classes required. Mix with brio
components freely: a <pre class="code"> inside a .prose block picks
up brio's code styling, not prose's default; a <details class="details">
picks up brio's details component rather than prose's lighter chrome.

Scoping rule: where an element is commonly given a class for
component identity (a, ul, ol, li, table), prose's selector narrows
to :not([class]) so classed elements keep their own styling. Prose
is for bare HTML; components own themselves. Matches the pattern in
reset.css (a:not([class]) inherits color) and means new components
don't have to override prose's specificity to work inside it.

Specificity rule: prose's element selectors (headings, p, blockquote,
code, hr, img, figure, dt/dd, kbd, abbr, mark, del/s, sup a) are
wrapped in :where() so they contribute zero specificity beyond the
.prose class. Same-layer component rules override without gymnastics.
Cross-layer ordering still applies -- a rule in a later layer beats
prose regardless of specificity, which is the desired behavior.

Details expand/collapse animation comes from details.css (applied
globally to every <details>) -- this file only adds prose-appropriate
visual chrome.

Reference

Source: src/css/prose.css

Applies to

.prose

Classes

ClassKindDescription
.is-widevariantRemoves the max-width limit for full-width layouts

Tokens

TokenDefaultDescription
--prose-margin-blockvar(--space-sm-md)Block-axis margin between direct children
--prose-max-width65chMaximum inline size for readable line length
--prose-border-colorvar(--color-border)Separator border color for rules, table rows, and details
--prose-border-widthvar(--border-width-default)Separator border width for rules, table rows, and details
--prose-blockquote-border-colorvar(--color-border-strong)Inline-start accent border color for blockquotes
--prose-blockquote-border-width3pxInline-start accent border width for blockquotes
--prose-kbd-border-colorvar(--color-border-strong)Border color for keyboard input elements
--prose-kbd-border-widthvar(--border-width-default)Border width for keyboard input elements
--prose-figcaption-alignstartText alignment for figure captions
--prose-mark-bgvar(--color-warning-subtle)Mark element background