Skip to main content
WireKit
Copy for LLM

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 but is fully driven by WireKit's CSS variable system, ensuring consistent theming across light and dark modes.

Usage

Prose Typography

Getting Started with WireKit

WireKit is a free, MIT-licensed UI component library for Laravel Livewire. It provides dozens of accessible, themeable components out of the box.

"The best component library is one that gets out of your way."
  • Design token driven
  • WCAG 2.1 AA accessible
  • Dark mode automatic

composer require pushery/wirekit

Rendering Markdown

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

<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.

{{-- 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.

Prose — readable measure

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 measure="none" to opt out and fill the container, or measure="wide" for a slightly roomier column.

Blog Post Example

<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 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.
Readability presets

Chat preset — tight leading, sized for a message bubble that already caps its own line length.

Reading preset — roomier leading for long-form text you sit with for a while.

Large preset — bigger body text, with the leading grown to match so it actually reads easier.

{{-- 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:

{{-- 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:

{{-- 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> — 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():

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 for the canonical vocabulary, alias matrix, and decision tree.

Further Reading

Was this page helpful?

Voting requires cookies or local storage. What we store