RoadieRoadie
  • Home
  • Brand
  • Foundations
  • Content
  • Tokens
  • Components
  • Charts
  • Widgets
  • Appearance
HomeBrandFoundations
Appearance

Components

  • Navigation

    • Breadcrumb
    • LinkBeta
    • NavigatorAlpha
    • Steps
    • Tabs
  • Layout

    • Accordion
    • CollapsibleBeta
    • PaneAlpha
    • ScrollArea
    • Separator
  • Actions

    • Button
    • IconButton
    • ToggleBeta
    • Toggle groupBeta
    • ToolbarBeta
  • Fields

    • EditorAlpha
    • Field
    • Fieldset
    • Input
    • Label
    • Number fieldBeta
    • OTP fieldBeta
    • Query fieldAlpha
    • Textarea
  • Choices

    • Autocomplete
    • CheckboxBeta
    • Combobox
    • Radio group
    • Select
    • SliderBeta
    • SwitchBeta
  • Date and time

    • CalendarBeta
    • CalendarTile
    • Countdown
    • Dashboard periodBeta
    • Date fieldBeta
    • Date pickerBeta
    • Date range pickerBeta
    • DateTime
    • Duration
    • Time fieldBeta
  • Collections

    • Card
    • Carousel
    • DataTableBeta
    • ListAlpha
    • Marquee
    • RecordGridAlpha
    • RecordTableAlpha
    • SortableAlpha
    • TableBeta
  • Overlays

    • Dialog
    • Drawer
    • MenuBeta
    • Popover
    • ToastBeta
    • Tooltip
  • Status

    • Badge
    • CalloutBeta
    • DeltaBeta
    • Empty state
    • MeterBeta
    • ProgressBeta
    • SkeletonBeta
    • SparklineBeta
    • StatTileBeta
  • Media & brand

    • AvatarBeta
    • Icon Tile
    • Image
    • Logo
    • PatternBeta
    • QR codeBeta
    • SpotIllustration
  • Text

    • Code
    • Highlight
    • KbdBeta
    • Mark
    • Prose

Prose

Status
Stable
Added
Components 2.0.08 Apr 2026
Updated
Components 2.16.020 Oct 2026
Docs updated
19 Oct 2026

Typesets rich HTML from a CMS, Markdown, or user content.

Prose renders the core .prose class, which styles the bare headings, paragraphs, lists, tables, code, quotes, rules, figures, and keys inside it. It sets the rhythm, a 65ch measure, and every colour from Roadie tokens.

Import

import { Prose } from '@oztix/roadie-components/prose'

Examples

Default

<Prose>
<h2>Ochre Kite Weekender 2026</h2>
<p>
Gates open at <strong>11am</strong> on Saturday. Bring a printed or{' '}
<a href='#'>mobile ticket</a>, and show it at the north gate.
</p>

Sizes

sm sets 14px body text, headings one step below the UI heading styles, and a tighter rhythm, for dense content such as an FAQ or a sidebar. md is the default, with the UI heading styles, for content inside a page such as an event description. lg sets larger body text with the prose heading styles, for editorial pages.

<Prose size='sm'>
<h3>Can I change the name on my ticket?</h3>
<p>Yes, up to 48 hours before doors. Open the ticket and choose Transfer.</p>
</Prose>
<Prose size='md'>
<h3>Getting there</h3>

Each size only sets --prose-* variables, so a class such as [--prose-flow:2em] on Prose adjusts it further. The variables table lists them all.

Custom element

Use render to draw Prose as a semantic element. The deprecated as prop still works the same way.

<Prose render={<article />}>
<h2>Conditions of entry</h2>
<p>
This renders as an <code>&lt;article&gt;</code> element instead of a{' '}
<code>&lt;div&gt;</code>.
</p>

Tables

Prose wraps each bare table in a .prose-scroll, which scrolls sideways instead of overflowing. Each column stays at least 9em wide, so a table that can't fit scrolls rather than squeezing its text. While a table overflows, its scroller is a named region in the tab order, so a keyboard user can reach it with Tab and scroll it with the arrow keys.

<Prose>
<h3 id='set-times'>Set times</h3>
<table>
<thead>
<tr>
<th>Session</th>

Prose finds the tables written as elements in its children, and the tables in HTML it renders, such as CMS output:

<Prose dangerouslySetInnerHTML={{ __html: page.body }} />

A table a component renders inside Prose isn't wrapped, because React owns it. Wrap that table in .prose-scroll yourself, and Prose still makes the scroller a region. A table already in .prose-scroll, .not-prose, or data-not-prose stays as it is.

Opting out

Add .not-prose or data-not-prose to an element inside Prose to keep the styles away from it and everything inside it. The prose foundation covers how an escaped child keeps its place in the rhythm.

A button or link button that renders an <a> inside Prose gets the prose underline. Wrap it in .not-prose to keep its own styles.

<Prose>
<p>Tickets go on sale Friday at 9am.</p>
<div className='not-prose flex flex-wrap gap-2'>
<Button href='#' intent='accent' emphasis='strong'>
Buy tickets
</Button>

Guidelines

  • Use Prose for rendered content, such as CMS output, Markdown, and user HTML. For app UI, use the display and text utilities directly.
  • A Prose inside a .not-prose element stays unstyled, so put the escape around the parts that need it, not around a region that also holds Prose.
  • Use render rather than as to set the element; as is removed in v3.
  • Give a table a caption, or put it under a heading with an id, so its region has a name that says what the table holds.

Accessibility

  • A table's scroller gets tabIndex={0} and role="region" only while the table overflows, and Prose checks again whenever its size changes. A table that fits adds no tab stop.
  • The region is named by the table's caption. Without one, it points aria-labelledby at the nearest heading before it that has an id. A later table under the same heading is named "Set times, table 2", and a table with no heading before it is "Table", then "Table 2".
  • The focused scroller shows the is-focusable ring.
  • The server renders the scroller without a tab stop, and Prose adds it after hydration, so the markup matches. Tables in rendered HTML are wrapped after hydration too.

Hooks

useScrollRegion

import { useScrollRegion } from '@oztix/roadie-components/scroll-region'
function useScrollRegion(ref: RefObject<HTMLElement | null>): void

Gives a scroll container outside Prose the same keyboard access as a Prose table. Put the ref on the element that scrolls, and add is-focusable for the ring.

function RefundTable() {
const ref = useRef<HTMLDivElement>(null)
useScrollRegion(ref)
return (
<div ref={ref} className='overflow-x-auto is-focusable'>
<table>…</table>

It returns nothing, and sets these attributes while the content overflows:

AttributeValue
tabindex0, unless the element already has a tabindex
roleregion, unless the element already has a role
aria-label or aria-labelledbyThe name Prose would give, unless the element already has one

API reference

Prose

Prose container for long-form/rich content (CMS output, markdown, user HTML). Renders the core `.prose` class, which styles the nested HTML. Wraps each bare table in a `.prose-scroll`, which keyboard users can reach and scroll while the table overflows.

asElementType
Deprecated

Deprecated: Use `render` instead. `as` will be removed in v3.0.0.

renderRoadieRenderProp

Swaps the underlying element with full control over its shape, such as `render={<article />}`.

classNamestring
size"sm" | "md" | "lg"

Body size and heading scale. `sm` sets headings one step below the UI heading styles, for dense content such as an FAQ; `md` uses the UI heading styles; `lg` uses the prose heading styles, for editorial pages.

Defaults to 'md'.

Previous page← MarkNext pageOverview →