Theme
On this page (14)
Component Skill — components/typography/component-skill.md

Native basis

Native HTML text elements: <h1>–<h4>, <p>, <blockquote>, <code>, <small>.

Web Platform APIs

<h1>–<h6><blockquote><code><small>text-wrap: balancetext-wrap: prettyhanging-punctuationLogical propertiesprefers-contrastforced-colors

Classes

.h1.h2.h3.h4.p.lead.large.small.muted.blockquote.inline-code

Accessibility

• Use heading levels in order (h1 → h2 → h3). Do not skip levels.

• Headings create the document outline used by screen readers for navigation.

• <blockquote> is announced as a quote by assistive technology - no extra ARIA needed.

• <code> is announced as code - no extra ARIA needed.

• prefers-contrast: more - removes tight letter-spacing, adds outline to inline code, thickens blockquote border, promotes muted text to foreground color.

• forced-colors: active - blockquote border and inline code adapt to system colors.

Notes

• These are utility classes for prose content - not a component with variants/sizes.

• The .h1–.h4 classes allow applying heading styles to non-heading elements when semantic headings aren't appropriate.

• Typography classes compose freely with other components (Card content, Dialog body, Alert description).

• text-wrap: balance is used on all headings (h1–h4) for better visual line distribution.

• text-wrap: pretty is used on paragraphs and lead text for orphan prevention.

• hanging-punctuation is used on blockquotes for optical quote alignment.

• Blockquote uses logical properties (border-inline-start, padding-inline-start) for automatic RTL support.

• For lists, use the List component (list.css) - typography does not ship its own list styles.

• For prose tables, use the Table component (table.css) - typography does not ship its own table styles.

§Headings

Four heading levels - h1 is extrabold, h2 includes a bottom border, h3 and h4 are semibold.

§Paragraph

Standard body text with generous line height.

§Lead

Larger, muted intro paragraph.

§Large / Small / Muted

Text size variations.

§Blockquote

Quoted block with left border and italic style.

§Inline code

Monospace code snippet within text.

§Font stacks

Three token-driven stacks - components reference the tokens, never a hardcoded family.

§Prose Composition

All typography classes combined in a full prose layout - matching the shadcn/ui demo.

§RTL support

Blockquotes use logical properties (border-inline-start, padding-inline-start) - they flip automatically in RTL contexts.

§Chinese

lang='zh-Hans' switches the paragraph to CJK rules: justified between characters, strict line breaks (no 。or , at a line start) and a line-end 。 or , kept inside a straight right edge (Safari hangs it, Chromium trims its blank half). Latin words and digits get their thin gap automatically (text-autospace); headings drop the Latin tracking, emphasis uses dots under the characters instead of italics.

§Chinese: aligning the line ends

The same paragraph twice in a narrow column. Left: start-aligned, the lines end ragged and a comma may be pushed to the next line's start. Right: the .p rules - justified, strict breaking, the line-end 。/, hung (Safari) or half-width (Chromium) - a straight edge.

§Japanese, top to bottom

text-vertical sets tategaki: columns run top to bottom, right to left. Ruby (furigana) sits beside the kanji, .text-upright stands a two-digit number upright in one cell (tate-chu-yoko), emphasis marks become sesame dots, and the heading, paragraph gap and blockquote rule turn with the text - they are logical properties.

§Traditional Chinese, vertical

The same vertical rules for zh-Hant: punctuation takes its vertical form (。、「」 rotate or reposition), bopomofo / pinyin ruby sits to the right.

§CSS view file

/* -- Typography component --------------------------------------- */
/* -- Base text ---------------------------------------------- */
/* Sets the default font size for the entire page.            */
/* All typography classes are sized relative to this baseline. */
@layer base {
  body {
    font-size: 0.875rem;
    line-height: 1.5;
  }
}
@layer components {
  /* -- Headings ---------------------------------------------- */
  .h1 {
    scroll-margin-top: 5rem;
    font-size: 2.25rem;
    font-weight: 800;
    letter-spacing: -0.025em;
    line-height: 1.15;
    text-wrap: balance;
    color: var(--foreground);
  }
  .h2 {
    scroll-margin-top: 5rem;
    font-size: 1.875rem;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.25;
    text-wrap: balance;
    color: var(--foreground);
    border-block-end: 1px solid var(--border);
    padding-block-end: 0.5rem;
    &:first-child { margin-block-start: 0; }
  }
  .h3 {
    scroll-margin-top: 5rem;
    font-size: 1.5rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    line-height: 1.3;
    text-wrap: balance;
    color: var(--foreground);
  }
  .h4 {
    scroll-margin-top: 5rem;
    font-size: 1.25rem;
    font-weight: 600;
    line-height: 1.4;
    text-wrap: balance;
    color: var(--foreground);
  }
  /* -- Paragraph --------------------------------------------- */
  .p {
    font-size: 0.875rem;
    line-height: 1.75;
    text-wrap: pretty;
    color: var(--foreground);
    &:not(:first-child) { margin-block-start: 1.5rem; }
  }
  /* -- Lead --------------------------------------------------- */
  .lead {
    font-size: 1.25rem;
    line-height: 1.6;
    color: var(--muted-foreground);
    text-wrap: pretty;
  }
  /* -- Large -------------------------------------------------- */
  .large {
    font-size: 1.125rem;
    font-weight: 600;
    color: var(--foreground);
  }
  /* -- Small -------------------------------------------------- */
  .small {
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1;
    color: var(--foreground);
  }
  /* -- Muted -------------------------------------------------- */
  .muted {
    font-size: 0.875rem;
    color: var(--muted-foreground);
  }
  /* -- Blockquote --------------------------------------------- */
  .blockquote {
    margin-block-start: 1.5rem;
    border-inline-start: 2px solid var(--border);
    padding-inline-start: 1.5rem;
    font-style: italic;
    hanging-punctuation: first last;
    color: var(--foreground);
    & p { margin: 0; }
  }
  /* -- Inline code -------------------------------------------- */
  .inline-code {
    position: relative;
    border-radius: var(--radius-sm);
    background: var(--muted);
    padding: 0.2rem 0.3rem;
    font-family: var(--font-mono);
    font-size: 0.875em;
    font-weight: 600;
    color: var(--foreground);
  }
  /* -- CJK: Chinese, Japanese, Korean ------------------------------
     Keyed on the lang attribute, so one set of classes serves every script.
     Latin tuning is undone (negative tracking, faux italics); paragraphs
     justify between characters with strict line breaking (kinsoku: no 。
     or , at a line start), and sentence-end punctuation may hang past the
     edge (Safari: hanging-punctuation) or trims to half width
     (Chromium: text-spacing-trim) so the justified edge stays straight.
     text-autospace adds the thin gap between CJK and Latin /
     digits that authors used to type by hand. */
  :is(.h1, .h2, .h3, .h4, .lead, .large):is(:lang(zh), :lang(ja), :lang(ko)),
  :is(:lang(zh), :lang(ja), :lang(ko)) :is(.h1, .h2, .h3, .h4, .lead, .large) {
    letter-spacing: 0;
  }
  /* Japanese headings break between phrases (bunsetsu), not mid-word */
  :is(.h1, .h2, .h3, .h4, .lead):lang(ja),
  :lang(ja) :is(.h1, .h2, .h3, .h4, .lead) {
    word-break: auto-phrase;
  }
  :is(.p, .lead, .blockquote):is(:lang(zh), :lang(ja), :lang(ko)),
  :is(:lang(zh), :lang(ja), :lang(ko)) :is(.p, .lead, .blockquote) {
    line-height: 1.9;
    line-break: strict;
    text-autospace: normal;
  }
  :is(.p, .blockquote):is(:lang(zh), :lang(ja)),
  :is(:lang(zh), :lang(ja)) :is(.p, .blockquote) {
    text-align: justify;
    text-justify: inter-character;
    /* the line-end 。,: Safari hangs it past the edge; Chromium trims the
       fullwidth glyph's blank half (text-spacing-trim) - either way the
       justified edge stays straight */
    hanging-punctuation: allow-end;
    text-spacing-trim: normal;
  }
  /* CJK has no italics - a slanted Han character is a synthetic oblique */
  .blockquote:is(:lang(zh), :lang(ja), :lang(ko)),
  :is(:lang(zh), :lang(ja), :lang(ko)) .blockquote {
    font-style: normal;
  }
  /* -- Vertical text (tategaki / 直排) ----------------------------------
     .text-vertical sets top-to-bottom lines flowing right to left. Latin
     runs lie sideways (text-orientation: mixed); .text-upright stands a
     short run (a two-digit number) upright in one cell - tate-chu-yoko.
     The spacing above uses logical properties, so headings, paragraphs and
     the blockquote rule turn with the text. Give the block a height. */
  .text-vertical {
    writing-mode: vertical-rl;
    text-orientation: mixed;
  }
  .text-upright {
    text-combine-upright: all;
  }
  /* -- Emphasis marks (圈点 / 傍点) ---------------------------------------
     Dots beside each character instead of italics: over the text in
     Japanese (right of it when vertical), under it in Chinese. */
  .text-emphasis {
    font-style: normal;
    text-emphasis: filled dot;
    text-emphasis-position: over right;
  }
  .text-emphasis:lang(ja) { text-emphasis-style: filled sesame; }
  .text-emphasis:lang(zh) { text-emphasis-position: under right; }
  /* ruby annotations (furigana, pinyin, zhuyin) read as small, muted type */
  :is(.p, .lead, .blockquote, .h1, .h2, .h3, .h4) rt {
    font-size: 0.5em;
    color: var(--muted-foreground);
    letter-spacing: 0;
  }
  /* -- Accessibility ------------------------------------------ */
  @media (prefers-contrast: more) {
    .h1, .h2, .h3, .h4 {
      letter-spacing: 0;
    }
    .blockquote {
      border-inline-start-width: 3px;
    }
    .inline-code {
      outline: 1px solid var(--border);
    }
    .muted {
      color: var(--foreground);
    }
  }
  @media (forced-colors: active) {
    .blockquote {
      border-inline-start-color: CanvasText;
    }
    .inline-code {
      background: Canvas;
      outline: 1px solid CanvasText;
      color: CanvasText;
    }
  }
}

Comments, ideas or improvements? Edit this page's source on GitHub