Skip to main content

Typography

A polymorphic text component for consistent headings, paragraphs, lead text, and inline code styling.

We moved the checkout service off the shared database last quarter. The migration took three weekends and one very long Friday, but read latency at the p95 dropped from 340ms to 90ms and the on call rotation stopped getting paged about lock contention.

1import { Typography } from '@/components/ui/typography';
2
3export function Default() {
4 return (
5 <div className="w-full max-w-md">
6 <Typography variant="p">
7 We moved the checkout service off the shared database last quarter. The migration took three
8 weekends and one very long Friday, but read latency at the p95 dropped from 340ms to 90ms
9 and the on call rotation stopped getting paged about lock contention.
10 </Typography>
11 </div>
12 );
13}

Installation

pnpm dlx nachui add typography

Anatomy

1import { Typography } from '@/components/ui/typography';
1<Typography variant="h1">Hello World</Typography>
2<Typography variant="p">Body copy goes here.</Typography>
3<Typography variant="muted">De-emphasized text.</Typography>

Examples

Default

We moved the checkout service off the shared database last quarter. The migration took three weekends and one very long Friday, but read latency at the p95 dropped from 340ms to 90ms and the on call rotation stopped getting paged about lock contention.

1import { Typography } from '@/components/ui/typography';
2
3export function Default() {
4 return (
5 <div className="w-full max-w-md">
6 <Typography variant="p">
7 We moved the checkout service off the shared database last quarter. The migration took three
8 weekends and one very long Friday, but read latency at the p95 dropped from 340ms to 90ms
9 and the on call rotation stopped getting paged about lock contention.
10 </Typography>
11 </div>
12 );
13}

Headings

Moving 40 packages into one repo

Why we stopped publishing internal packages

Versioning without a release train

What broke in CI on the first day

Cache keys and lockfile drift
Appendix: migration checklist
1import { Typography } from '@/components/ui/typography';
2
3const outline = [
4 { variant: 'h1' as const, text: 'Moving 40 packages into one repo' },
5 { variant: 'h2' as const, text: 'Why we stopped publishing internal packages' },
6 { variant: 'h3' as const, text: 'Versioning without a release train' },
7 { variant: 'h4' as const, text: 'What broke in CI on the first day' },
8 { variant: 'h5' as const, text: 'Cache keys and lockfile drift' },
9 { variant: 'h6' as const, text: 'Appendix: migration checklist' },
10];
11
12export function Headings() {
13 return (
14 <div className="flex w-full max-w-md flex-col gap-4">
15 {outline.map((entry) => (
16 <Typography key={entry.variant} variant={entry.variant}>
17 {entry.text}
18 </Typography>
19 ))}
20 </div>
21 );
22}

Lead, Large & Muted

Rebuilding search on a small budget

By Lucia Mendez, 6 min read, updated Mar 14

Our old search ran on a managed cluster that cost more than the rest of the stack combined. We replaced it with Postgres full text search and kept the results people actually clicked.

Queries got 40ms slower. The bill went from $1,900 a month to $60.

1import { Typography } from '@/components/ui/typography';
2
3export function LeadMuted() {
4 return (
5 <article className="flex w-full max-w-md flex-col gap-3">
6 <Typography variant="h3">Rebuilding search on a small budget</Typography>
7 <Typography variant="muted">By Lucia Mendez, 6 min read, updated Mar 14</Typography>
8 <Typography variant="lead">
9 Our old search ran on a managed cluster that cost more than the rest of the stack combined.
10 We replaced it with Postgres full text search and kept the results people actually clicked.
11 </Typography>
12 <Typography variant="large">
13 Queries got 40ms slower. The bill went from $1,900 a month to $60.
14 </Typography>
15 </article>
16 );
17}

Polymorphic (as prop)

Release 2.4.0
Published Mar 14, 214 downloads in the first hour

Dialogs now restore focus to the element that opened them. Run pnpm add @acme/ui@2.4.0 to pick up the fix.

1import { Typography } from '@/components/ui/typography';
2
3export function CustomTag() {
4 return (
5 <div className="border-border bg-card flex w-full max-w-md flex-col gap-2 rounded-xl border p-5">
6 {/* Looks like a heading, but stays a div so the page outline is untouched. */}
7 <Typography as="div" variant="h4">
8 Release 2.4.0
9 </Typography>
10 <Typography as="span" variant="muted">
11 Published Mar 14, 214 downloads in the first hour
12 </Typography>
13 <Typography variant="p" className="[&:not(:first-child)]:mt-3">
14 Dialogs now restore focus to the element that opened them. Run{' '}
15 <Typography variant="code">pnpm add @acme/ui@2.4.0</Typography> to pick up the fix.
16 </Typography>
17 </div>
18 );
19}

API Reference

Typography

PropTypeDefaultDescription
variant'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'blockquote' | 'lead' | 'large' | 'small' | 'muted' | 'code''p'Text style variant
asReact.ElementType-Override the rendered HTML element
align'left' | 'center' | 'right' | 'justify'-Text alignment
weight'light' | 'normal' | 'medium' | 'semibold' | 'bold' | 'extrabold'-Font weight override
classNamestring-Additional CSS classes
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue
Ctrl+I