---
title: Prose
description: Typography wrapper for raw HTML content
visibility: guest
draft: false
---

# Prose

The `<x-wirekit::prose>` component is a typography wrapper that styles raw HTML content (headings, paragraphs, lists, blockquotes, code blocks, tables, links, images, and horizontal rules) using WireKit design tokens. It serves a similar purpose to [@tailwindcss/typography](https://github.com/tailwindlabs/tailwindcss-typography) but is fully driven by WireKit's CSS variable system, ensuring consistent theming across light and dark modes.

## Usage

:::preview{title="Prose Typography"}
<x-wirekit::prose>
    <h2>Getting Started with WireKit</h2>
    <p>WireKit is a free, MIT-licensed UI component library for
       <a href="https://livewire.laravel.com">Laravel Livewire</a>.
       It provides dozens of accessible, themeable components out of the box.</p>
    <blockquote>
        "The best component library is one that gets out of your way."
    </blockquote>
    <ul>
        <li>Design token driven</li>
        <li>WCAG 2.1 AA accessible</li>
        <li>Dark mode automatic</li>
    </ul>
    <p><code>composer require pushery/wirekit</code></p>
</x-wirekit::prose>
:::

### Rendering Markdown

Prose is ideal for wrapping rendered Markdown (e.g., from `Str::markdown()` or a CMS):

```blade
<x-wirekit::prose>
    {!! Str::markdown($article->body) !!}
</x-wirekit::prose>
```

### Size Variants

The `size` prop adjusts the base font size while keeping all other proportions relative.

```blade
{{-- Small prose — compact text --}}
<x-wirekit::prose size="sm">
    <p>Smaller body text for captions or footnotes.</p>
</x-wirekit::prose>

{{-- Default (medium) --}}
<x-wirekit::prose>
    <p>Standard body text for articles and documentation.</p>
</x-wirekit::prose>

{{-- Large prose — expanded reading --}}
<x-wirekit::prose size="lg">
    <p>Larger body text for landing pages or hero sections.</p>
</x-wirekit::prose>
```

### Reading Measure

Long-form text reads best at a constrained line length. `prose` clamps to a readable ~65-character measure by default (the `--measure-wk` token), so body copy stays comfortable on wide screens instead of stretching edge to edge. Pass `measure="wide"` (~78ch) for a roomier column, or `measure="none"` to fill the full container width.

:::preview{title="Prose — readable measure"}
<x-wirekit::prose>
    <p>This paragraph is capped at a readable line length by default, so long-form text stays comfortable to read on a wide screen instead of running the full width of its container. Set <code>measure="none"</code> to opt out and fill the container, or <code>measure="wide"</code> for a slightly roomier column.</p>
</x-wirekit::prose>
:::

### Blog Post Example

```blade
<article>
    <x-wirekit::prose size="lg">
        <h1>{{ $post->title }}</h1>
        <p>Published on {{ $post->published_at->format('F j, Y') }}</p>

        {!! $post->rendered_html !!}
    </x-wirekit::prose>
</article>
```

## Size Reference

| Size | CSS Variable | Typical Use |
| --- | --- | --- |
| `sm` | `--text-wk-sm` | Footnotes, captions, sidebars |
| `md` | `--text-wk-md` | Articles, documentation (default) |
| `lg` | `--text-wk-lg` | Landing pages, hero sections |

## Styled Elements

The prose wrapper applies design-token-driven styles to the following HTML elements using Tailwind's child selector syntax (`[&_element]`):

| Element | Styles Applied |
| --- | --- |
| `h1` | 2XL font size, heading weight, tight leading, bottom margin |
| `h2` | XL font size, heading weight, tight leading, top + bottom margin |
| `h3` | LG font size, heading weight, tight leading, top + bottom margin |
| `h4` | MD font size, heading weight, top + bottom margin |
| `p` | Bottom margin |
| `a` | Accent color, underline with 2px offset |
| `strong` | Heading font weight |
| `ul` | Disc markers, left padding, bottom margin |
| `ol` | Decimal markers, left padding, bottom margin |
| `li` | Bottom margin (xs) |
| `blockquote` | Left border (4px), left padding, italic, muted text, bottom margin |
| `code` | Mono font, smaller size, muted background, inline padding, rounded |
| `pre` | Muted background, rounded, padded, bottom margin, horizontal scroll |
| `pre code` | Transparent background, no padding (inherits from `pre`) |
| `table` | Full width, collapsed borders, bottom margin |
| `th` | Left-aligned, heading weight, padding, double bottom border |
| `td` | Padding, subtle bottom border |
| `hr` | Border color from tokens, vertical margin (xl) |
| `img` | Rounded corners, vertical margin |

## Why Prose Instead of @tailwindcss/typography?

The [@tailwindcss/typography](https://github.com/tailwindlabs/tailwindcss-typography) plugin is excellent, but it uses its own hardcoded color palette. WireKit's Prose component differs in important ways:

1. **Token-driven** — every color, size, radius, and spacing value comes from WireKit's `--color-wk-*`, `--text-wk-*`, `--padding-wk-*`, etc. variables
2. **Auto dark mode** — because all values are CSS variables, dark mode works automatically via the `.dark` class on any ancestor element; no `dark:` prefixes needed
3. **Themeable** — changing your WireKit theme (e.g., switching accent colors or font families) automatically updates all prose typography
4. **Personalizable** — use `WireKit::personalize()` to override prose styles at the class level

## Readability presets

`preset` is a **separate axis** from `size` / `density` / `measure` — it tunes
readability and composes with them rather than overriding them.

Each preset moves **size and leading together**, on purpose: bumping font-size
alone makes large text read *worse*, not better.

| Preset | For | What it does |
| --- | --- | --- |
| `chat` | A message bubble | Tighter leading. Adds no measure clamp — the bubble already caps line length, so a clamp would only fight it. |
| `reading` | Long-form articles | Roomier leading for sustained reading. |
| `large` | Users who scale text up | Larger body text **and** leading grown to match. |

:::preview{title="Readability presets"}
<x-wirekit::stack gap="lg">
    <x-wirekit::prose preset="chat"><p>Chat preset — tight leading, sized for a message bubble that already caps its own line length.</p></x-wirekit::prose>
    <x-wirekit::prose preset="reading"><p>Reading preset — roomier leading for long-form text you sit with for a while.</p></x-wirekit::prose>
    <x-wirekit::prose preset="large"><p>Large preset — bigger body text, with the leading grown to match so it actually reads easier.</p></x-wirekit::prose>
</x-wirekit::stack>
:::

```blade
{{-- 1. A chat bubble body — the bubble caps the width, prose sets the rhythm --}}
<x-wirekit::prose preset="chat" size="sm">
    {!! $markdownHtml !!}
</x-wirekit::prose>
```

## Streaming

Prose is **stable while text streams in**: appending paragraphs, headings, lists
or code blocks never moves the text the reader is already on. The block rhythm
uses bottom-margins, which cannot reflow earlier siblings.

That means a `wire:stream` of rendered markdown needs no special mode:

```blade
{{-- 1. Blocks append as they arrive; nothing above them shifts --}}
<x-wirekit::prose preset="chat">
    <span wire:stream="answer">{!! $answerHtml !!}</span>
</x-wirekit::prose>
```

## Typographic polish

Where the browser supports it, headings are balanced (`text-wrap: balance`) and
paragraphs avoid short last lines (`text-wrap: pretty`). This is **progressive
enhancement**: it is feature-detected, purely additive, and browsers without it
get the normal wrapping they always had — there is no shim and nothing depends
on it.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `string` | `'md'` | Base font size: `'sm'`, `'md'`, `'lg'` |
| `variant` | `string` | `'default'` | Text tone: `'default'`, `'muted'` |
| `density` | `string` | `'comfortable'` | Heading and paragraph spacing rhythm: `'comfortable'` (long-form article), `'compact'` (tighter, for marketing pages) |
| `measure` | `string` | `'default'` | Readable line-length clamp: `'default'` (~65ch), `'wide'` (~78ch), `'none'` (full container width) |
| `preset` | `string\|null` | `null` | Readability tuning on top of size/density/measure: `'chat'` (tight leading), `'reading'` (roomier leading), `'large'` (bigger text and leading) |
| `scope` | `string\|null` | `null` | Scoped personalization key |

## Keyboard Interaction

This component is a layout wrapper. Keyboard interaction is delegated to its children.

### Code blocks wrap; they do not scroll

A `<pre>` inside `<x-wirekit::prose>` wraps long lines instead of scrolling horizontally.
That is deliberate: a horizontal scroll region has to be reachable by keyboard, and Prose
styles markup it did not author — it cannot put a tab stop on your `<pre>`. A scrolling
block with no tab stop hides the rest of the line from anyone who is not using a pointer.

If you want horizontal scrolling, author the keyboard model yourself and Prose will honor
it:

```blade
{{-- 1. tabindex makes the block a tab stop, so the hidden part of the line is reachable. --}}
{{-- 2. role + aria-label tell a screen reader what the region is. --}}
{{-- 3. Prose sees the tabindex and switches this block back to horizontal scrolling. --}}
<x-wirekit::prose>
    <pre tabindex="0" role="region" aria-label="Example configuration"><code>{{ $longLine }}</code></pre>
</x-wirekit::prose>
```

For code you control, prefer [`<x-wirekit::code-block>`](code-block.md) — it ships the
scroll region already wired, with a copy button and language labeling.

## Design Tokens

| Token | Used for |
| --- | --- |
| `--font-wk-sans` | Prose body font family |
| `--font-wk-mono` | Inline `<code>` + `<pre>` font family |
| `--font-wk-heading-weight` | Heading weight |
| `--text-wk-sm` / `--text-wk-md` / `--text-wk-lg` / `--text-wk-xl` | Body / lead / heading font sizes |
| `--leading-wk-relaxed` | Body line height |
| `--leading-wk-tight` | Heading line height |
| `--color-wk-text` | Body text |
| `--color-wk-text-muted` | Blockquote text |
| `--color-wk-accent` | Inline `<a>` color |
| `--color-wk-bg-muted` | Inline `<code>` + `<pre>` background |
| `--color-wk-border` / `--color-wk-border-subtle` | Blockquote left bar + table cell border |
| `--radius-wk-sm` / `--radius-wk-md` | Inline `<code>` + `<pre>` border radius |
| `--padding-wk-x-sm` / `--padding-wk-x-md` / `--padding-wk-x-lg` | Block / inline padding |
| `--padding-wk-y-xs` / `--padding-wk-y-sm` / `--padding-wk-y-md` / `--padding-wk-y-lg` / `--padding-wk-y-xl` | Vertical rhythm between blocks |

## Personalization

Override classes globally via `WireKit::personalize()`:

```php
use Pushery\WireKit\WireKit;

WireKit::personalize('prose', [
    'base' => 'font-sans text-gray-900 leading-relaxed [&_a]:text-blue-600 [&_a]:underline',
]);
```

## Usage & Conventions

> **Prop conventions** — this component uses one or more of the shared semantic prop names (`intent` / `variant` / `tone` / `surface`). See [Prop naming conventions](/extending/prop-naming-conventions) for the canonical vocabulary, alias matrix, and decision tree.

## Further Reading

- [@tailwindcss/typography](https://github.com/tailwindlabs/tailwindcss-typography) — the Tailwind plugin that inspired this component's purpose
- [MDN: CSS child combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_combinator) — how the `[&_element]` selector works in Tailwind v4
