Skip to Content

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.

CodeSituationElementclassName
TYPOGRAPHY-1Root name of the page or routeh1text-xl font-semibold tracking-tight
TYPOGRAPHY-2First outline depth under the pageh2text-base font-semibold
TYPOGRAPHY-3Local subsection inside a sectionh3text-sm font-medium
TYPOGRAPHY-4Final admitted outline depthh4text-xs font-medium text-muted-foreground
TYPOGRAPHY-5One short title for the single dominant object of a regiondivtext-base font-medium text-foreground
TYPOGRAPHY-6Title of a repeated, compact, long or localizable peer objectdivtext-sm font-medium text-foreground
TYPOGRAPHY-7Ordinary UI copy: a description, a metadata line, a valueptext-sm leading-5 font-normal text-foreground
TYPOGRAPHY-8Prose whose job is sustained readingptext-base leading-6 font-normal text-foreground
TYPOGRAPHY-9Copy that only qualifies a primary line or surfaceptext-xs leading-4 font-normal text-muted-foreground
TYPOGRAPHY-10A marker that partitions a result stream without creating a sectiondivtext-sm leading-5 font-normal text-muted-foreground
TYPOGRAPHY-11Text a control already ownsthe control’s own elementno typography class
TYPOGRAPHY-12No outline depth and no declared ownerptext-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

  1. 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.
  2. 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.
  3. 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.
  4. 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-1 through TYPOGRAPHY-4.
  5. 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.
  6. 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.
  7. 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: an h2 names a SECTION of the page; an h1 names the WHOLE page. If any other line encloses it, it is not TYPOGRAPHY-1.
  • TYPOGRAPHY-5: an object title does NOT enter the outline. The same string is TYPOGRAPHY-1 on the detail route of that object and TYPOGRAPHY-6 when 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: an h3 sits INSIDE an h2. With no h2 above it in the same tree, it is not yet an h3.
  • TYPOGRAPHY-5: same text-base recipe but a different WEIGHT and a different ELEMENT. An h2 is font-semibold and enters the outline; an object title is font-medium and 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 h2 above 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. An h3 inside a small section is still an h3.
  • TYPOGRAPHY-6: same text-sm but a different weight and a different element. An h3 is font-medium AND in the outline; a peer title is font-medium and NOT in the outline. If the name is the name of a DATA OBJECT — a course, a file, a person — it is TYPOGRAPHY-6, not an h3.

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 → h3 chain 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 are text-xs and both are muted. They differ in two places, and both are binding: TYPOGRAPHY-4 is font-medium and a REAL h4 in the outline; TYPOGRAPHY-9 is font-normal and NOT a heading element. If the line does not need to appear in the page’s list of headings, it is not an h4.
  • 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 an h2 merely 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 always TYPOGRAPHY-6. The safe default is TYPOGRAPHY-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; an h3 names a STRUCTURAL item. Same text-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 still TYPOGRAPHY-7; one paragraph in an article meant for reading is TYPOGRAPHY-8.
  • TYPOGRAPHY-9: TYPOGRAPHY-9 LOSES ITS MEANING when detached from the line it qualifies; TYPOGRAPHY-7 does 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: same text-base but a different weight. font-medium is a name, font-normal is content. A paragraph is never font-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: same text-xs and same muted tone. They differ in WEIGHT and in ELEMENT: an h4 is font-medium and sits in the outline; supporting copy is font-normal and 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 an h3, and the page outline instantly fills with Yesterday, 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, an a, an input, 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-11 WINS OVER ALL OF THEM. Even when the string reads exactly like TYPOGRAPHY-7 copy, inside a control is inside a control.
  • TYPOGRAPHY-9: a hint line OUTSIDE the field is TYPOGRAPHY-9. Placeholder text INSIDE the field is TYPOGRAPHY-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-12 is 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

InputEvidence required
outlinenone · 1 · 2 · 3 · 4 — whether the line is a rung of the document outline, and which
ownerpage · section · dominant-object · repeated-peer · ui-copy · reading-prose · control · partition
relationshipindependent · qualifies-primary · partitions-results
repetitionWhether the line appears once in the region or repeats as one of many peers
length riskWhether the string can grow long, wrap, or grow under localization
stateready · 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

  1. Outline depth decides BOTH the semantic element AND the visible rank. An h2 styled as body, or a div styled as a heading, is a violation in both directions.
  2. Four heading depths are the whole outline. A fifth means the content must flatten.
  3. One bounded region has exactly ONE typographic lead.
  4. TYPOGRAPHY-5 requires BOTH facts: a single object AND a stably short title. Missing one drops to TYPOGRAPHY-6.
  5. A repeated, compact, long or localizable title ALWAYS takes the peer recipe.
  6. text-xs always means muted supporting copy. There is no foreground text-xs, and no muted text-xs that is not support — except the h4 of TYPOGRAPHY-4, which is font-medium and is in the outline.
  7. Numbers, hover, label length, available space and breakpoints never promote rank.
  8. Loading, empty, error, localization, responsive and theme preserve the settled code.
  9. Rank is never manufactured with a border, background, badge or box. Those state a surface, not a rank.
  10. Text a control owns takes no typography override from outside.
  11. No unlisted size, weight or tone recipe is assembled. The vocabulary is closed.
  12. 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-11 wins 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 like TYPOGRAPHY-7 copy.
  • 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 is TYPOGRAPHY-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-12 and 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-6
line: 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-10
line: <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-5
line: <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-7
line: 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-7

When 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.