defuss-shadcn / Primitives / typography
TypographyATM
Styles for headings, paragraphs, lists, blockquotes, and inline code. Built on native HTML text elements with utility classes. No JavaScript required.
On this page (14)
§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