The .prose class styles a block of bare HTML so
headings, lists, blockquotes, tables, and inline elements look
coherent without per-element class wiring. Drop it on the
container around long-form content -- a blog post, a
documentation article, a CMS-rendered page, an email body --
and the descendant typography handles itself.
Classed elements inside .prose keep their own
identity: a <pre class="code"> picks up
brio's code component styling rather than prose's bare-pre
treatment; a <table class="table"> keeps
the table component; a
<details class="details"> gets brio's
full details chrome. Prose only paints when no class is
declared.
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 tiresome and
the markup forgets itself by the third article. Bare HTML
inside a single .prose wrapper is faster to
write, faster to read, and survives copy-paste from external
sources without re-classing every element.
What it doesn't do
Prose isn't a layout primitive. It doesn't constrain
containers, build columns, or reflow content responsively
beyond setting a comfortable max-line-length
(65ch). For full-width docs pages -- where
tables and code examples want the whole viewport -- pair with
.prose.is-wide.
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
Author the markup.
Wrap in .prose.
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-spacing 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-spacing
--space-sm-md
Vertical rhythm between direct children.
--prose-max-width
65ch
Readable line length for long-form content.
--prose-blockquote-border
3px solid --color-border-strong
Inline-start accent on blockquotes.
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
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.
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 tiresome and
the markup forgets itself by the third article. Bare HTML
inside a single <code>.prose</code> wrapper is faster to
write, faster to read, and survives copy-paste from external
sources without re-classing every element.
</p>
<h3>What it doesn't do</h3>
<p>
Prose isn't a layout primitive. It doesn't constrain
containers, build columns, or reflow content responsively
beyond setting a comfortable max-line-length
(<code>65ch</code>). For full-width docs pages -- where
tables and code examples want the whole viewport -- pair with
<code>.prose.is-wide</code>.
</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><details></code>, <code><kbd></code>, <code><abbr></code>, <code><mark></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-spacing</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><table class="table"></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-spacing</code></td>
<td><code>--space-sm-md</code></td>
<td>Vertical rhythm 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</code></td>
<td><code>3px solid --color-border-strong</code></td>
<td>Inline-start accent on blockquotes.</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><details></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><details></code>); prose only adds the
visual chrome.
</p>
<p>
Reach for <code><details class="details"></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
Prose's element selectors are scoped with
:not([class]) on the elements where consumers
routinely add classes (anchors, lists, tables). A
classed component inside .prose keeps its own
styling.
Inside a prose block
A bare paragraph and a bare list look like prose. Drop a
classed component in and it keeps its identity:
Class
Wins
<table class="table">
Brio's table component (this row).
<table>
Prose's quiet treatment (the table above).
Same rule for unordered lists -- a classed
<ul class="cluster"> stays a horizontal
cluster instead of a vertical disc list.
Composition wins
Even inside prose
No specificity fight
Code blocks pick up brio's
code component -- header,
syntax tokens, copy button -- when they have the class:
<article class="prose">
<h2>Inside a prose block</h2>
<p>
A bare paragraph and a bare list look like prose. Drop a
classed component in and it keeps its identity:
</p>
<table class="table is-striped is-bordered">
<thead>
<tr>
<th>Class</th>
<th>Wins</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><table class="table"></code></td>
<td>Brio's table component (this row).</td>
</tr>
<tr>
<td><code><table></code></td>
<td>Prose's quiet treatment (the table above).</td>
</tr>
</tbody>
</table>
<p>
Same rule for unordered lists -- a classed
<code><ul class="cluster"></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 fight</span></li>
</ul>
<p>
Code blocks pick up brio's
<a href="/brio/components/code">code component</a> -- header,
syntax tokens, copy button -- when they have the class:
</p>
<pre class="code"><code class="language-html"><article class="prose">
<h2>A heading</h2>
<p>A paragraph</p>
</article></code></pre>
</article>
.is-wide
The default --prose-max-width is
65ch -- comfortable for body copy. Reference
pages, dashboards, and any context where tables or wide
code samples need the full container should opt into
.prose.is-wide, which removes the inline-size
cap.
Wide prose
Wide prose still inherits the typography rhythm, list
styling, and inline-element chrome -- it just doesn't
clamp the inline-size. Use on pages where the content
has its own width discipline (cards, grids,
<docs-resize> demos), and the prose
clamp would awkwardly orphan the wide content.
<article class="prose is-wide">
<h3>Wide prose</h3>
<p>
Wide prose still inherits the typography rhythm, list
styling, and inline-element chrome -- it just doesn't
clamp the inline-size. Use on pages where the content
has its own width discipline (cards, grids,
<code><docs-resize></code> demos), and the prose
clamp would awkwardly orphan the wide content.
</p>
</article>
Customization
Eight scoped tokens shape prose:
--prose-spacing,
--prose-max-width,
--prose-border,
--prose-blockquote-border,
--prose-kbd-border,
--prose-mark-bg, and
--prose-figcaption-align. Override on the
.prose root for per-instance tuning, or on
:root to retone every prose block in a
project.
All element selectors inside prose are wrapped in
:where() so they contribute zero specificity
beyond the .prose class itself. Same-layer
component rules override prose without specificity
gymnastics, and component classes win automatically when
used inside prose blocks.