Skip to content

Components

Typography

A small type system on the theme's tokens. Heading separates the semantic level from the visual size, Text covers body copy in sizes, tones and weights, and Prose styles raw HTML or MDX with no plugin.

Source
Engineering

Shipping a design system people actually use

Components are the easy part. Adoption comes from docs, defaults and a migration path that respects the code teams already have.

We started by auditing every button in the product and found 41 variations. Six months later there are three, all imported from @/components/ui/button, and nobody had to stop shipping to get there.

Maya Chen · 6 min read · Updated March 12

Installation

pnpm dlx shadcn@latest add @desyne/typography

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import {
  Blockquote,
  Code,
  Heading,
  Lead,
  List,
  Prose,
  Small,
  Text,
} from "@/components/ui/typography";
tsx
<Heading level={1}>Billing</Heading>
<Lead>Plans, invoices and payment methods for your workspace.</Lead>
<Text tone="muted">Changes apply at the start of the next billing cycle.</Text>

None of these are client components, so they render in server components with no JavaScript.

When to use

  • Heading — every title in an app screen or page. Pick level for the document outline and size for the look.
  • Text, Lead, Small, Code — body copy you write in JSX, where you want the scale and tones without repeating class strings.
  • Prose — content you don't write as JSX: MDX, Markdown rendered to HTML, CMS rich text, release notes from an API. It styles plain <h2>, <p>, <ul> and friends.
  • Inside components such as Card or Dialog, use their own title and description parts; they already follow this scale.

Anatomy

PartRendersNotes
Heading<h1>–<h6>level picks the element (default 2). size picks the style; without it each level has a default size.
Text<p>as switches to span, div, strong, em or small. Sizes xs–lg, six tones, four weights.
Lead<p>Larger, muted intro paragraph under a page title.
Small<small>Fine print: legal lines, timestamps, footnotes.
Code<code>Inline code. Sized in em, so it matches the surrounding text.
Blockquote<blockquote>With attribution, wrapped in <figure> with a <figcaption>.
List<ul> / <ol>ordered for numbers. Put <li> elements inside.
Prose<div>Styles the HTML elements inside it with descendant selectors.

The scale

SizeFont size / line heightDefault forTypical use
display36px → 48px from sm, 1.05Hero titles, big numbers
xl30px → 36px from sm, 1.25h1Page titles
lg24px, 1.375h2Section titles
md20px, 1.375h3Card and subsection titles
sm18px, 1.375h4Small groups, sidebars
xs16px, 1.5h5, h6Inline headings, list titles

Body text runs xs 12/20, sm 14/24, md 16/28 (default) and lg 18/32. Headings are font-semibold with tighter tracking from md up; all sizes are standard Tailwind steps, so they follow your theme's --text-* values if you've changed them.

display48px / 1.05

Quarterly planning

Hero titles, marketing
xl36px / 1.25

Quarterly planning

Page titles (h1)
lg24px / 1.375

Quarterly planning

Section titles (h2)
md20px / 1.375

Quarterly planning

Card and subsection titles (h3)
sm18px / 1.375

Quarterly planning

Small groups (h4)
xs16px / 1.5

Quarterly planning

Inline headings (h5, h6)

Examples

Level and size

The outline and the look are separate decisions. A settings card might hold an h2 that should look small; a stat might be an h3 that should look huge. Set level for screen readers and the page outline, size for the eye. Don't skip levels to get a smaller heading.

Billing

Rendered as <h2> with the sm size, so the card stays compact without skipping a level in the outline.

99.98%

Rendered as <h3> with the display size: a big number that's still a small heading.

Text sizes and tones

tone sets the color from the theme: default for primary reading, subtle for softer body copy, muted for captions and helper text, plus brand, danger and success. default, subtle and muted all meet WCAG AA contrast for body text in light and dark mode.

Sizes

lg — Intro copy and roomy layouts.

md — The default for reading.

sm — Dense UI, cards and tables.

xs — Captions and footnotes.

Tones

default — Primary reading color.

subtle — Softer body copy.

muted — Metadata and helper text.

brand — Emphasis that matches links.

danger — Something went wrong.

success — Saved and synced.

Inline elements

Code for identifiers, file names and commands; Small for fine print; Text as="strong" or as="span" for emphasis inside other text.

Run npx shadcn@latest add @desyne/typography, then import Heading and Text wherever you'd reach for a raw <h2> or <p>. Press ⌘K to search the docs.

Prices exclude VAT. Annual plans save 20%.

© 2026 Wrenly Inc. Wrenly is a registered trademark. Terms apply.

Lists and quotes

List keeps native list semantics and uses muted markers. marker="none" removes the bullets but keeps it announced as a list. Blockquote with attribution becomes a <figure> with a caption, the recommended markup for a quote with a source.

Before you launch

  1. Point your domain at the new deploy.
  2. Invite your team and assign roles.
  3. Turn on two-factor authentication.

Included in Pro

  • Unlimited projects
  • Audit log with 1-year retention
  • SAML single sign-on
We replaced three internal libraries with one. New hires ship their first screen in a day instead of a week.
— Priya Raman, Head of Design at Northwind

Prose

Wrap rendered HTML or MDX in Prose. It styles headings (h1–h4), paragraphs, links, bold text, lists (nested too), inline code, code blocks, blockquotes, horizontal rules, tables, images, figures and kbd. The first and last child lose their outer margin so the article sits flush in its container. Cap the line length with max-w-prose (65 characters).

Migrating to the new billing API

The v2 billing API replaces per-seat line items with usage records. Existing subscriptions keep working until June 30, but new features only ship on v2.

Timeline

  1. Now: v2 is available behind the billing_v2 flag.
  2. April 15: new workspaces default to v2.
  3. June 30: v1 endpoints return 410 Gone.

Updating your client

Swap the subscription item call for a usage record:

await billing.usage.create({
  subscription: "sub_42",
  quantity: seats.length,
  timestamp: Date.now(),
});

Usage records are idempotent per timestamp, so retries never double charge.

Field changes

v1 fieldv2 fieldNotes
seatsquantityNow reported, not set.
planpricePrices are versioned.
  • Webhooks keep the same signatures.
  • Invoices show usage per day.
    • Totals are unchanged.

Questions? Reply to the migration email or open a ticket from Settings → Support.

Prose sizes

size sets the base font size; every margin and heading inside is in em, so the whole article scales together. Use sm in side panels and comments, lg for long-form reading.

Release notes · 4.2

Comments now support threads and @mentions. Everything else scales with the size you pick, because the article measures in em.

  • Resolve a thread to collapse it.
  • Mentions notify by email after five minutes.

With MDX

Pass Prose as the wrapper for your MDX content:

app/blog/[slug]/page.tsxtsx
import { Prose } from "@/components/ui/typography";

export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const { default: Content, title } = await import(`@/content/${slug}.mdx`);
  return (
    <Prose className="mx-auto max-w-prose">
      <Content />
    </Prose>
  );
}

For HTML from a CMS, sanitize it first and render it with dangerouslySetInnerHTML on the Prose element. To drop a styled component into an article (a Card, a chart) without the article styles reaching it, close Prose before it and open a new one after.

Guidance

  • One h1 per page, then h2 for sections and h3 inside them. Use size to adjust looks, never the level.
  • Keep lines short. 45–75 characters reads best: max-w-prose on articles, max-w-xl or max-w-2xl on intros.
  • Use tone for hierarchy before size. A muted line under a title usually reads better than a smaller font.
  • Don't put long text in color. brand, danger and success are for short emphasis; pair them with an icon or wording so color isn't the only cue.
  • Balance and pretty. Headings use text-wrap: balance and body text text-wrap: pretty to avoid lonely last words.
  • Numbers line up with tabular-nums in tables and stats.

Accessibility

  • Heading always renders a real heading element, so screen reader users can jump between headings and the outline matches what they see.
  • List keeps list semantics; with marker="none" it adds role="list" because Safari otherwise stops announcing unstyled lists.
  • Blockquote with attribution uses <figure> and <figcaption> so the source is associated with the quote.
  • Links inside Prose are underlined, not only colored.
  • Text sizes are in rem and em, so they follow the user's browser font size setting.

Styling

Data slots

SlotElement
data-slot="heading"Heading
data-slot="text"Text
data-slot="lead"Lead
data-slot="small"Small
data-slot="code"Code
data-slot="blockquote"Blockquote (the <figure> is blockquote-figure)
data-slot="list"List
data-slot="prose"Prose

Reusing the styles

headingVariants, textVariants and proseVariants are exported, so you can apply the scale to other elements:

tsx
import { headingVariants, textVariants } from "@/components/ui/typography";

<Link className={headingVariants({ size: "sm" })} href="/changelog">Changelog</Link>
<label className={textVariants({ size: "sm", tone: "muted" })}>Optional</label>

To change a Prose rule, edit the matching line in proseVariants (they're grouped by element), or add a descendant class at the call site: <Prose className="[&_a]:no-underline">.

API Reference

Heading

Prop

Type

Text

Prop

Type

Blockquote

Prop

Type

List

Prop

Type

Prose

Prop

Type

Lead, Small and Code take the native props of <p>, <small> and <code>.

  • Card — titles and descriptions inside a card.
  • Kbd — keyboard keys and shortcuts.
  • Link — standalone links with variants.