Duck Prose
The long-form surface. Styles markup it has never seen — bare headings, paragraphs, quotes, tables — from the theme tokens, at a measure that stays readable.
Preview
The pond at scale
Every component in this registry styles markup it can see. Prose styles markup it cannot: the content arrives as bare tags from an MDX file or a CMS, so the rules are descendant selectors on one wrapper — and every one of them is wrapped in :where(), which means a utility on any element inside still wins.
A measure of 68 characters is not a preference. It is where a line stops being pleasant to read.Typographic folklore, repeatedly confirmed
What it reads from the theme
- --font-display for headings, --font-mono for code and table headers.
- --primary for links, list markers and the blockquote rule.
- --radius for anything boxed, so a square theme squares the code blocks too.
| Element | Treatment |
|---|---|
blockquote | Ruled, upright, muted |
th | Mono, uppercase, tracked |
Installation
The CLI writes the source into your project and pulls in whatever it depends on.
pnpm dlx shadcn@latest add @duck/duck-prosePulls in @duck/theme. Already installed items are skipped.
Usage
import { DuckProse } from "@/components/ui/duck-prose"The file lands in components/ui/duck-prose.tsx and belongs to you from that point on. Edit it in place rather than wrapping it.
Props
Everything not listed here is forwarded to the underlying element.
| Prop | Type | Default | Description |
|---|---|---|---|
| measure | "default" | "wide" | "full" | "default" | 68ch, 76ch, or no limit. 68 is where a line stops being pleasant to read; full is for a table-heavy page. |
| as | "div" | "article" | "section" | "main" | "div" | The element. A post is an article, not a div inside one. |
Rules
What keeps this component from turning into noise.
- Every rule ships inside :where(), so it is zero specificity: a utility on any element inside prose wins. That is deliberate — a prose wrapper that cannot be overridden is a cage.
- Tables are the one thing prose cannot fix from outside: a wide table has to scroll in a box of its own, and CSS cannot add that box. Wrap it in .duck-prose-scroll — in MDX, map `table` once.
- @tailwindcss/typography is not used and not needed here. It ships its own scale, greys and blockquote, none of which are a theme's.
