Typography
You are given a plain request in prose — “a course page with a curriculum section listing modules” — and you return, for every line of text that request implies, one situation code, one element and one className. The request never states a size and you never estimate one: the size, the weight and the tone follow from what the line OWNS.
Law
A line of text states what it OWNS. Choose its size, weight and tone from that ownership, never from how prominent it should look.
Two facts decide, and only two: outline depth — whether the line is a rung of the document outline — and content ownership — whether the line names an object, states a fact, qualifies another line, partitions a stream, or belongs to a control that already owns its own text.
Visual preference, numeric shape, label length, hover, available space, breakpoint and screenshot geometry select nothing.
This is binding, not advisory. Every rendered line of text falls under exactly one code below.
There is no line too small to have one: a twelve-character caption under a metric is TYPOGRAPHY-9
for the same reason a route name at the top of a page is TYPOGRAPHY-1. “It is only one word” is not
an exemption — it is the most common place the rule gets skipped, because a single word is exactly
where a writer reaches for whatever size looks right.
Situation codes
Every situation this module governs carries a code, TYPOGRAPHY-<index>. The code names the
SITUATION; the element and className columns name what that situation emits. They are not the same
thing, and one of them emits nothing.
| Code | Situation | Element | className |
|---|---|---|---|
TYPOGRAPHY-1 | Root name of the page or route | h1 | text-xl font-semibold tracking-tight |
TYPOGRAPHY-2 | First outline depth under the page | h2 | text-base font-semibold |
TYPOGRAPHY-3 | Local subsection inside a section | h3 | text-sm font-medium |
TYPOGRAPHY-4 | Final admitted outline depth | h4 | text-xs font-medium text-muted-foreground |
TYPOGRAPHY-5 | One short title for the single dominant object of a region | div | text-base font-medium text-foreground |
TYPOGRAPHY-6 | Title of a repeated, compact, long or localizable peer object | div | text-sm font-medium text-foreground |
TYPOGRAPHY-7 | Ordinary UI copy: a description, a metadata line, a value | p | text-sm leading-5 font-normal text-foreground |
TYPOGRAPHY-8 | Prose whose job is sustained reading | p | text-base leading-6 font-normal text-foreground |
TYPOGRAPHY-9 | Copy that only qualifies a primary line or surface | p | text-xs leading-4 font-normal text-muted-foreground |
TYPOGRAPHY-10 | A marker that partitions a result stream without creating a section | div | text-sm leading-5 font-normal text-muted-foreground |
TYPOGRAPHY-11 | Text a control already owns | the control’s own element | no typography class |
TYPOGRAPHY-12 | No outline depth and no declared owner | p | text-base font-normal text-foreground |
TYPOGRAPHY-11 IS A SITUATION, NOT A RECIPE. A button label, a badge, a link, a field placeholder
and a status chip carry typography that the control itself settled. Re-declaring a size on that text
from the outside claims an ownership the caller does not have, and the claim is invisible until the
control changes and one call site stops matching every other. The code exists because “the control
already decided” is a case a reader must be able to recognise, cite and be corrected against — a
situation with no name is a situation nobody can be shown to have got wrong.
TYPOGRAPHY-12 IS A FLOOR, NOT AN ESCAPE. It is the readable answer when a request genuinely
declares no owner, so that a public table never returns a refusal. It is not permission to skip the
ownership question when the answer is available in the request.
The outline stops at four. There is no fifth heading depth, and adding one is a rule change rather than a shortcut: a content structure that needs depth five is a content structure that has to flatten. A closed ladder forces an ownership decision; an open one invites inventing a rung, which is taste re-entering through arithmetic.
Reading a request
- List the lines the request states. “A course page with a curriculum section listing modules, each module showing its title and its lesson count” states four kinds of line: the page name, the section name, each module title, and each lesson-count line.
- Do not invent a line the request never mentions. A tagline, a breadcrumb or a footnote is not in that request. Resolve what is stated; resolve the rest when it arrives.
- Resolve outermost first — page name, then section names, then the lines inside each section. A line never inherits the code of the line above it; ownership is decided one line at a time.
- For each line ask the outline question first. Is this line a rung of the document outline, and
which rung? If it is, the depth alone settles
TYPOGRAPHY-1throughTYPOGRAPHY-4. - If it is not in the outline, ask the ownership question in the section for each remaining code. The first code whose situation matches is the answer.
- If two adjacent codes both match, choose the code that claims LESS ownership — the peer over the dominant, the support over the section, the UI copy over the reading prose. Ask one discriminating question only when the requester explicitly requires the larger claim.
- One region mixing several ownerships is normal. A card holding an object name, a qualifying line and a description resolves to three codes, not to one averaged code. One line, one ownership.
TYPOGRAPHY-1 — root name of the page
Situation. This line answers “where am I?”. It is the root of the document outline and each route has exactly ONE of them.
Recognition signs
- Delete it and the user no longer knows what the page is about.
- It matches or nearly matches the last breadcrumb, the browser tab title, the route name.
- Everything else on the page sits BELOW it in the outline, including things that look larger.
- On a detail route, the object’s own name IS the page name.
Ask yourself. Is this line the outline root of the current route?
Boundary
TYPOGRAPHY-2: anh2names a SECTION of the page; anh1names the WHOLE page. If any other line encloses it, it is notTYPOGRAPHY-1.TYPOGRAPHY-5: an object title does NOT enter the outline. The same string isTYPOGRAPHY-1on the detail route of that object andTYPOGRAPHY-6when listed among siblings on an index route. The route decides, not the string.
Common business situations. Dashboard page name · report page name · course name on a course detail route · profile name on a public profile route · order name on an order route · article name on an article route · settings page title.
TYPOGRAPHY-2 — first outline depth under the page
Situation. A part of the page with its own purpose and its own content, which MUST appear in the outline so a screen-reader user can jump to it.
Recognition signs
- People call it by name when they talk: “the reviews section”, “the recent activity section”.
- It can be empty on its own, error on its own, load on its own.
- It is a direct child of the page in the outline, not a child of another section.
Ask yourself. Is this a first-level part of the page a user needs to jump straight to?
Boundary
TYPOGRAPHY-1: see above.TYPOGRAPHY-3: anh3sits INSIDE anh2. With noh2above it in the same tree, it is not yet anh3.TYPOGRAPHY-5: sametext-baserecipe but a different WEIGHT and a different ELEMENT. Anh2isfont-semiboldand enters the outline; an object title isfont-mediumand does not. This is the most confused pair in the upper half of the scale.
Common business situations. Overview · Recent activity · Course content · Student reviews · Billing · Security · Team members · Attachments · Frequently asked questions.
TYPOGRAPHY-3 — local subsection
Situation. A small group INSIDE a section, independent enough to need a name, but not large enough to be a first-level part of the page.
Recognition signs
- There is always an
h2above it in the same tree. - It gathers a few lines into one small topic.
- Remove it and the section still has content, only the group loses its name.
Ask yourself. Does this group sit inside an already-named section, and does it need a name of its own?
Boundary
TYPOGRAPHY-2: count the depth, not the size of the area. Anh3inside a small section is still anh3.TYPOGRAPHY-6: sametext-smbut a different weight and a different element. Anh3isfont-mediumAND in the outline; a peer title isfont-mediumand NOT in the outline. If the name is the name of a DATA OBJECT — a course, a file, a person — it isTYPOGRAPHY-6, not anh3.
Common business situations. Personal details / Contact details inside a profile page · Level / Topic / Duration inside a filter rail · Shipping address / Billing address inside a checkout section · Prerequisites inside an introduction section.
TYPOGRAPHY-4 — final outline depth
Situation. The fourth outline depth, and the last. At this depth the name is nearly a label: it still has to be present in the outline, but it must no longer compete for the eye.
Recognition signs
- A full
h1 → h2 → h3chain sits above it. - It names a criterion, a sub-item of a sub-item.
- A keyboard user still needs to jump to it.
Ask yourself. Does this line truly need to be in the outline, or is it only qualifying copy?
Boundary
TYPOGRAPHY-9: this is the most dangerous boundary in the module. Both aretext-xsand both are muted. They differ in two places, and both are binding:TYPOGRAPHY-4isfont-mediumand a REALh4in the outline;TYPOGRAPHY-9isfont-normaland NOT a heading element. If the line does not need to appear in the page’s list of headings, it is not anh4.- Fifth depth: it does NOT exist. Needing depth five means the content structure has to flatten. This is the one request the module answers with a question rather than a class.
Common business situations. A scoring criterion name under a criteria group · a small step name inside a process that already has three depths · a field name inside a deeply nested field group · a sub-item name of a clause.
TYPOGRAPHY-5 — the single dominant object
Situation. A large region exists to talk about ONE object, and that object has a SHORT name. That name leads the whole region but does NOT enter the document outline, because it is data, not structure.
Recognition signs
- There is exactly ONE such title in the region, with no second one beside it.
- The string is short, and stably short — not “short at the moment”.
- Every other line in the region is describing this object.
Ask yourself. Does this region hold exactly ONE object, and is its name stably short? Missing ONE
of the two facts drops it to TYPOGRAPHY-6.
Boundary
TYPOGRAPHY-2: an object title does not enter the outline. Do not turn it into anh2merely because it is the largest line in the region.TYPOGRAPHY-6: BOTH facts — single and short — are required. Repeated, compact, long, or liable to grow under localization is alwaysTYPOGRAPHY-6. The safe default isTYPOGRAPHY-6.
Common business situations. A course name in a large introduction card · a plan name in a single-plan comparison panel · a file name in a preview pane · an object name in a detail side panel · a campaign name in a summary block.
TYPOGRAPHY-6 — title of a peer object
Situation. The name of an object standing AMONG MANY OF ITS KIND, or a name that can grow long. The scanning rhythm must be even across the lines, so no line may be larger than another.
Recognition signs
- It comes from a loop, or will come from a loop in the near future.
- It can be long, it can wrap, it can grow after translation.
- Its container is a list, a grid, a table.
Ask yourself. Is this name one of many peer names, or does it carry a length risk?
Boundary
TYPOGRAPHY-5: see above. A wider layout does NOT promote a peer into a dominant.TYPOGRAPHY-3: a peer title names a DATA OBJECT; anh3names a STRUCTURAL item. Sametext-sm font-medium, different element and different outline status.TYPOGRAPHY-7: a title NAMES (font-medium), copy STATES (font-normal). If the line is a sentence, it is not a title.
Common business situations. A lesson title in a lesson list · a file name in a file list · an article title in a feed · a member name in a member list · a module name in a module grid · a notification title in a notification list · an order name in order history.
TYPOGRAPHY-7 — ordinary UI copy
Situation. A line that STATES A FACT: a short description, metadata, a value, a textual status. The user SCANS it rather than READS it.
Recognition signs
- It still means something on its own — it does not need a line above it in order to exist.
- It is data, or a description of data, not the name of the data.
- The user glances and takes the information away, without stopping to read sentence by sentence.
Ask yourself. Does this line stand on its own as a fact, and is the reader’s job to scan rather than to read continuously?
Boundary
TYPOGRAPHY-6: see above.TYPOGRAPHY-8: they differ by the READER’S JOB, not by length. Three sentences in a card meant for glancing are stillTYPOGRAPHY-7; one paragraph in an article meant for reading isTYPOGRAPHY-8.TYPOGRAPHY-9:TYPOGRAPHY-9LOSES ITS MEANING when detached from the line it qualifies;TYPOGRAPHY-7does not. This is the only test needed.
Common business situations. A short description of an item · 28/42 lessons done in a summary ·
an email address in an information line · a textual status in a detail line · a quantity in a cart
summary line · a creator name in a metadata line that means something on its own.
TYPOGRAPHY-8 — prose for sustained reading
Situation. Several sentences, several paragraphs, and the user’s job is to READ FROM START TO FINISH. The larger size and looser line height are not because the paragraph matters more, but because the eye has to run the whole length of every line.
Recognition signs
- Several paragraphs or more, or article / documentation / long explanation content.
- The user stops here rather than skimming past.
- Delete a paragraph and meaning is lost, not a detached fact.
Ask yourself. Must the reader read continuously across many sentences to understand?
Boundary
TYPOGRAPHY-7: see above. Length is not the criterion — the reading job is.TYPOGRAPHY-5: sametext-basebut a different weight.font-mediumis a name,font-normalis content. A paragraph is neverfont-medium“for emphasis”.
Common business situations. An article body · a text lesson body · a multi-paragraph concept explanation · terms of service · a changelog written as prose · the long description of a course.
TYPOGRAPHY-9 — copy that only qualifies
Situation. This line EXISTS ONLY BECAUSE of another line or another surface. Pull it away from the primary line and it says nothing.
Recognition signs
- There is always a primary line directly above it or directly beside it.
- Read alone, the question “what is this about?” has no answer.
- It is a timestamp, a unit, a handle, a provenance, a caption, a secondary count.
Ask yourself. If the line above were deleted, would this line still say anything? If not —
TYPOGRAPHY-9.
Boundary
TYPOGRAPHY-7: see above.TYPOGRAPHY-4: sametext-xsand same muted tone. They differ in WEIGHT and in ELEMENT: anh4isfont-mediumand sits in the outline; supporting copy isfont-normaland is not a heading.TYPOGRAPHY-10: supporting copy attaches to ONE line; a partition marker divides A STREAM and belongs to no single line.
Inseparable pair. text-xs and text-muted-foreground travel together. There is no
foreground-toned supporting copy, and no un-muted text-xs — except exactly TYPOGRAPHY-4.
Common business situations. Updated 12 minutes ago under a title · a handle under a display
name · a unit under a number · PDF · 2.4 MB under a file name · VAT included under a price · the
source of a quotation · a caption under a chart · a hint line under an input field.
TYPOGRAPHY-10 — result-stream partition marker
Situation. A continuous result stream needs landmarks for scanning — Today, Yesterday,
August — but those landmarks do NOT create additional sections in the document. They divide time,
not structure.
Recognition signs
- Generated by DATA, not by the page design: as many days as there are, that many markers.
- No table of contents should list them.
- Remove all the markers and the list is still correct, only harder to scan.
Ask yourself. Does this marker add an entry to the document outline? If not — TYPOGRAPHY-10.
Boundary
TYPOGRAPHY-2/TYPOGRAPHY-3: this is where the mistake is made most often. The marker looks like a heading, so it gets written as anh3, and the page outline instantly fills withYesterday,July. A marker is NEVER a heading element.TYPOGRAPHY-9: see above.
Common business situations. Today / Yesterday in an activity feed · monthly grouping in
transaction history · initial letters in a sorted contact list · This week in a task list · group
labels in a clustered search result list.
TYPOGRAPHY-11 — text a control already owns
Situation. The string lives INSIDE a control: a button label, badge text, link text, a field placeholder, status chip text. That control already settled its own typography.
Recognition signs
- The string sits inside a
button, ana, aninput, or an element with a control / status role. - Change that text and behaviour changes, not only content.
- The same control appears in many other places and must look identical everywhere.
Ask yourself. Does this string sit inside a control that already set its own typography?
Boundary
- Every other code:
TYPOGRAPHY-11WINS OVER ALL OF THEM. Even when the string reads exactly likeTYPOGRAPHY-7copy, inside a control is inside a control. TYPOGRAPHY-9: a hint line OUTSIDE the field isTYPOGRAPHY-9. Placeholder text INSIDE the field isTYPOGRAPHY-11.
No typography class is emitted. This is the code that emits NO RECIPE. Writing a size over a button label claims an ownership the call site does not have, and the mistake stays invisible until the day the control changes and one call site drifts away from every other.
Common business situations. Button label · badge text · counter chip text · tab label · placeholder text · navigation link text · toggle label · menu item text.
TYPOGRAPHY-12 — no owner declared
Situation. The request genuinely states NO outline depth and NO content owner, and no heading or supporting semantics can be inferred either. A readable answer is needed rather than a refusal.
Recognition signs
- The request speaks only about appearance: “make it stand out”, “make this a bit bigger”.
- It cannot be determined whom this line belongs to, and there is no surrounding line to compare against.
Ask yourself. Is the owner genuinely un-inferable, or has the question simply not been asked?
Boundary
- Every other code:
TYPOGRAPHY-12is a FLOOR, not an escape. If the request declares an owner anywhere, use the correct code rather than dropping here for speed.
No self-promotion by number. A numeric value does NOT become the leading line merely because it is a number. For it to lead, a content decision must say that it leads.
Common business situations. A “make 4.9/5 stand out” request that never says what leads the region · a line of text pasted in before it is attached to any structure · placeholder content while a content decision is pending.
Inputs
| Input | Evidence required |
|---|---|
| outline | none · 1 · 2 · 3 · 4 — whether the line is a rung of the document outline, and which |
| owner | page · section · dominant-object · repeated-peer · ui-copy · reading-prose · control · partition |
| relationship | independent · qualifies-primary · partitions-results |
| repetition | Whether the line appears once in the region or repeats as one of many peers |
| length risk | Whether the string can grow long, wrap, or grow under localization |
| state | ready · loading · empty · error — recorded to prove parity, never to change the answer |
outline and owner are required. relationship, repetition and length risk are required
whenever they are what separates two adjacent codes.
Rules
- Outline depth decides BOTH the semantic element AND the visible rank. An
h2styled as body, or adivstyled as a heading, is a violation in both directions. - Four heading depths are the whole outline. A fifth means the content must flatten.
- One bounded region has exactly ONE typographic lead.
TYPOGRAPHY-5requires BOTH facts: a single object AND a stably short title. Missing one drops toTYPOGRAPHY-6.- A repeated, compact, long or localizable title ALWAYS takes the peer recipe.
text-xsalways means muted supporting copy. There is no foregroundtext-xs, and no mutedtext-xsthat is not support — except theh4ofTYPOGRAPHY-4, which isfont-mediumand is in the outline.- Numbers, hover, label length, available space and breakpoints never promote rank.
- Loading, empty, error, localization, responsive and theme preserve the settled code.
- Rank is never manufactured with a border, background, badge or box. Those state a surface, not a rank.
- Text a control owns takes no typography override from outside.
- No unlisted size, weight or tone recipe is assembled. The vocabulary is closed.
- If two adjacent codes both remain reasonable, choose the one that claims LESS ownership — the peer over the dominant, the support over the section, the UI copy over the reading prose. Ask only when the request requires the larger claim.
Beyond these: a situation code maps to exactly one recipe, no recipe serves two codes, and every rendered line resolves to exactly one code. No text is out of scope.
Exceptions
Exceptions are PART of the rule, not relief from it. Each is closed and cites the situation it applies to.
- Control-owned text.
TYPOGRAPHY-11wins over every other code. If the string lives inside a button, badge, link, field or status control, the control’s recipe stands and no free-text class is emitted, even when the string reads exactly likeTYPOGRAPHY-7copy. - Page name that is also an object name. On a detail route, the object’s own name IS the page
name and takes
TYPOGRAPHY-1. The same string listed among siblings on an index route isTYPOGRAPHY-6. The route decides, not the string. - Fifth heading depth. Do not emit anything. Ask the author to flatten the outline. This is the one request the module answers with a question rather than a class.
- Numeric value with no declared owner. A number does not promote itself. Emit
TYPOGRAPHY-12and ask which line leads the region only when promotion is actually being requested. - State parity. Skeleton, empty and error renderings of the same content keep the same code. A skeleton that changes rank is lying about ownership while it waits.
- Long copy without a reachability policy. Truncation versus wrapping is not a typography decision. Keep the settled code and ask whether the full value must remain reachable.
- Two adjacent codes both match. Choose the code that claims LESS ownership — the peer over the dominant, the support over the section, the UI copy over the reading prose. Ask one discriminating question only when the requester explicitly requires the larger claim.
Output
One block per line of text, outermost first:
line: <the text being classified>
outline: <none | 1 | 2 | 3 | 4>
owner: <page | section | dominant-object | repeated-peer | ui-copy | reading-prose | control | partition>
relationship: <independent | qualifies-primary | partitions-results>
situation: <TYPOGRAPHY-1 … TYPOGRAPHY-12>
element: <h1 | h2 | h3 | h4 | div | p | none — the control's own>
className: <exact closed recipe, or none>
reason: <business fact that excludes the adjacent code>Worked example
Request. “The detail page of the course System Design Mastery, with a Course content section listing the modules; each module shows its title, a line with its lesson count and duration, and a Continue button.”
The request states five kinds of line: the page name, the section name, each module title, the lesson-count line under each title, and the button label. It states no article prose, no date marker and no fourth outline depth, so none of those are resolved.
line: System Design Mastery
outline: 1
owner: page
relationship: independent
situation: TYPOGRAPHY-1
element: h1
className: text-xl font-semibold tracking-tight
reason: this is the detail route of that object, so no other name encloses it, which excludes TYPOGRAPHY-6line: Course content
outline: 2
owner: section
relationship: independent
situation: TYPOGRAPHY-2
element: h2
className: text-base font-semibold
reason: the page design creates this part and a user must be able to jump to it, which excludes TYPOGRAPHY-10line: <module title>
outline: none
owner: repeated-peer
relationship: independent
situation: TYPOGRAPHY-6
element: div
className: text-sm font-medium text-foreground
reason: the title comes from a row among many rows and can grow after translation, which excludes TYPOGRAPHY-5line: <lesson count · duration>
outline: none
owner: ui-copy
relationship: qualifies-primary
situation: TYPOGRAPHY-9
element: p
className: text-xs leading-4 font-normal text-muted-foreground
reason: read away from the module title the line answers nothing, which excludes TYPOGRAPHY-7line: Continue
outline: none
owner: control
relationship: independent
situation: TYPOGRAPHY-11
element: none — the control's own
className: none
reason: the string sits inside the button and the control already settled its typography, which excludes TYPOGRAPHY-7When the request later adds a long multi-paragraph description meant to be read from start to finish,
that body is TYPOGRAPHY-8. When it adds This month above a grouped review stream, that marker is
TYPOGRAPHY-10. If it asks only that a rating “stand out” without saying what leads the region, the
answer is TYPOGRAPHY-12.
Scope
This module states a rule true of any front end. It names no product, no component library, no
registry key and no repository. Every example is ordinary markup with an ordinary className. Where
a recipe uses text-foreground or text-muted-foreground, those are the two content tones any theme
must define; a front end that spells them as literal palette steps substitutes its own two names and
the law is unchanged.