Components/Typography

Typography

The text scale as sixteen components, every style bound to font-size and font-weight because a typography token breaks line-height in Penpot.

Typography · the text scaleLive in the browser · drawn to match the file
H1 Breakpoint
H1
Ship faster with a shared kit
48/48 · 800 · text.5xl.font-size · font-weight.extrabold
H2
Everything in one place
30/36 · 600 · text.3xl.font-size · font-weight.semibold
H3
Components and tokens
24/32 · 600 · text.2xl.font-size · font-weight.semibold
H4
How the scale works
20/28 · 600 · text.xl.font-size · font-weight.semibold
Lead
A lead paragraph that introduces the section.
20/28 · 400 · text.xl.font-size · font-weight.normal · base.muted-foreground
Large
Large text
18/28 · 600 · text.lg.font-size · font-weight.semibold
P
A paragraph of body copy at the base size.
16/28 · 400 · text.base.font-size · font-weight.normal
Small
Small text
14/20 · 500 · text.sm.font-size · font-weight.medium
Muted
Muted text
14/20 · 400 · text.sm.font-size · font-weight.normal · base.muted-foreground
Two bindings, not one

Every style binds font-size and font-weight separately - text.3xl.font-size and font-weight.semibold - rather than one typography token carrying the whole style. That is deliberate: a typography token applied in Penpot takes the line-height with it and breaks it, so the kit binds the two attributes that survive and leaves the third alone.

Line-height is set, not bound

40 on H1 mobile, 48 on desktop, then 36, 32, 28 down the headings and 28 on P - the source's leading-7. They are correct values held as numbers, so a change to the type scale has to be made in two places rather than one. It is the one compromise on this page, and it is the medium's rather than the kit's.

H1 is the only responsive one

36 on mobile and 48 on desktop, which is the source's text-4xl with lg:text-5xl. Nothing else in the scale changes with the viewport - a 30px H2 works at every width, and giving every heading a breakpoint would double the component count for no gain.

Beyond the scale

Five styles that are more than type

A blockquote has a rule beside it, a list has bullets, code has a surface, and a table has rows that alternate. These carry geometry as well as type, which is why each is its own component rather than another line on the scale.

Style
This is blockquote

A 2px base.border rule with the text inset 24 from the edge of the component. The source also sets it in italic and the file does not, for a reason worth knowing: Geist ships no italic cut, so there is nothing to switch to. A browser will fake one by slanting the roman; Penpot will not, and a synthetic oblique is not something to bake into a component anyway.

The usage frame

The whole scale as one article

832 by 1068, holding one Rich Text instance at a 672 reading width. Rich Text is the sixteenth component and the only one made entirely of the other fifteen - eleven nested instances, so a change to H2 reaches the article without anyone opening it.

Ship faster with a shared kit

A design system is only useful while it stays consistent. This page is the type half of that: one scale, bound to tokens, used the same way in every component.

Everything in one place

Headings, body copy, quotes, lists and code, each drawn once and instanced everywhere else.

This is blockquote

Components and tokens

Every style below binds font-size and font-weight, and sets its line-height as a number.

• This is a list • This is a list • This is a list

The article is 672 wide because that is roughly 75 characters at 16px, which is where a line stops being comfortable to read.

How the scale works

Pick the style by what the text is, not by how big you want it to look.

672 reading width11 nested instancesRich Text is 636 × 788 in the file
Four places it earns its keep

Pick the style by what the text is

Not by how big you want it to look. A scale only stays useful while everyone picks from it for the same reasons, and the fastest way to lose that is to reach for H2 because H3 looked small in one particular place.

Example

Installation

How to add the kit to a project that already exists.

Requirements

Node 20 or newer, and a Tailwind v4 project. Run npx shadcn init before anything else.

Last updated 7 September 2026

Four styles doing four jobs: H1 names the page, Lead says what it is about in one sentence, H2 opens a section, and Muted carries the metadata nobody reads until they need it. Lead is the one people skip, and a docs page without it makes every reader scan the first paragraph to find out whether they are in the right place.

Right to left

The scale is direction-blind, the blocks are not

Nine of the styles are a size, a weight and a colour, and none of those has a direction. What does have one is everything the blocks add around the type: the blockquote's rule, the list's bullet, and the table's first column.

Direction

Components and tokens

A design system is only useful while it stays consistent, and consistency in type is a matter of picking the style by what the text is.

This is blockquote
• This is a list • This is a list • This is a list
This is a textThis is a text
This is a textThis is a text
This is a textThis is a text
This is a textThis is a text
Size and weight have no side

Every one of the nine scale steps is a font-size, a font-weight and a fill. Setting dir on an ancestor moves the text to the other edge and changes nothing else, which is why the scale needs no mirrored variants.

The quote rule is a child, not a border

Drawn as a 2px rectangle before the text rather than as border-left, it moves to the reading edge on its own. If you rebuild it in code, reach for border-inline-start and padding-inline-start - border-l and pl-6 will leave the rule stranded on the wrong side.

The bullet is a character

Because it is typed into the line rather than generated by list-style, it follows the text direction like any other glyph. That is the one place where the file's workaround behaves better than the real thing would need to be told to.

In the file

Sixteen components, twenty variants

Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.

The scaleone component eachH1 (Breakpoint: Mobile 36/40 · Desktop 48/48) · H2 30/36 with a base.border rule under it · H3 24/32 · H4 20/28 · Lead 20/28 · Large 18/28 · P 16/28 · Small 14/20 · Muted 14/2010
The blocksone component eachBlockquote · List · InlineCode 94 × 26 · Code 573 × 152 · Table / Item (Variant × Even, 4) · Table 314 × 1609
Typography / Rich Textnone636 × 788 - eleven nested instances of the other fifteen1

Using the Typography

Because a typography token applied in Penpot takes the line-height with it and breaks it. So each style binds font-size and font-weight separately - text.3xl.font-size and font-weight.semibold - and sets its line-height as a plain number. It is the one compromise on this page, and it belongs to the medium rather than to the kit.

40 on H1 mobile and 48 on desktop, then 36, 32, 28 down the headings, 28 on Lead and Large, 28 on P - the source's leading-7 - 24 on Blockquote and List, 20 on Small and Muted, and 24 on the code block, which is the loosest of the lot at 1.71 and right for text a reader scans by column as much as by line. They are correct values held as numbers, so a change to the scale has to be made in two places.

Because it is the only one that needs to be. 36 on mobile and 48 on desktop is the source's text-4xl with lg:text-5xl. A 30px H2 works at every width, and giving every heading a Breakpoint axis would double the component count without changing a single page.

Lead is the one sentence under a page title that says what the page is about - 20/28 in base.muted-foreground, one per page, directly under the H1. P is everything else. A docs page without a Lead makes every reader scan the first paragraph to work out whether they are in the right place, which is exactly the job Lead does at a glance.

Weight and colour, and what they are for. Small is 14 at medium in base.foreground - a label, something you want read. Muted is 14 at normal in base.muted-foreground - metadata, something available but not competing. P is 16 for prose. Body copy at 16 inside a card is almost always too big; that is what Small and Muted exist for.

Because the source does - h2 is the only heading with border-b and pb-2, which puts a 1px base.border 8 under the text and takes the component to 45 tall. It is what separates a section from a subsection at a glance in a long article, and it is the reason H2 and H3 are not interchangeable even though their sizes are only six apart.

Because Geist ships no italic cut. The source writes the blockquote in italic, and a browser will fake one by slanting the roman - Penpot will not, and a synthetic oblique is not a thing to bake into a component. The 2px rule and the 24 inset are doing the work instead, which are the parts a reader actually notices.

Because Penpot has no list primitive, so the marker is typed into the line rather than generated. It works, and under dir=rtl it follows the text like any other glyph - but it does not hang outside the text block the way list-disc does, so a wrapped line starts under the bullet rather than beside it. Rebuild it as a real list in code.

The article one. Typography / Table is a 314 × 160 block of four rows built from Typography / Table / Item, whose axes are Variant - Head or Cell - and Even, which fills the row with base.muted for zebra striping. The Table page's component is the data one, with sortable heads and eleven cell types. They look similar and are not interchangeable.

Showing the scale doing its job. It is the sixteenth component and the only one made entirely of the other fifteen - eleven nested instances at 636 wide, in the order a real article uses them. Change H2 and the article follows without anyone opening it, which is the whole argument for building it as instances rather than as text.

About 672, which is roughly 75 characters at 16px and what the usage frame uses. Narrower than 480 and the eye jumps back too often; past 900 it loses the start of the next line. The same paragraph at three widths reads three ways with no change to the type at all, which is worth seeing once.

The scale does, entirely - a font-size, a font-weight and a fill have no direction, so dir on an ancestor is the whole job. The blocks are where the work is: the blockquote rule is drawn as a rectangle before the text rather than as border-left, so it moves on its own, and in code it wants border-inline-start with padding-inline-start. The table's first column and the list's bullets follow the text the same way.

Data display · 6 variants