CSS can now make selections about sort that used to take a seat outdoors a reset. text-wrap: fairly can forestall a stranded phrase, container question models can scale a heading to the column it occupies, and text-box can trim the half-leading constructed right into a line field. A few of these options are experimental and browser help is uneven, in order that they want guardrails relatively than blind inclusion.
I’ve put collectively tidy-typography.css, which gathers the helpful elements right into a default stylesheet: rendering and fallback guidelines, fluid sizing, vertical rhythm, wrapping, hyphenation, and OpenType element.
Use it complete, or take the declaration that solves the actual irritation in entrance of you. I’ll stroll you thru all the main points on this article and provide the full code on the finish.
Rendering and fallback sanity
html {
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-synthesis: none;
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
physique {
font-size-adjust: from-font;
}
font-synthesis: none stops a browser from inventing daring or italic when the household lacks that face. The smoothing and text-rendering declarations are rendering hints, primarily for WebKit; outcomes nonetheless range by platform.
text-size-adjust disables cell font inflation that may make a intentionally sized format leap on rotation. font-size-adjust: from-font makes use of font metrics to scale back the obvious dimension change whereas a fallback swaps for a webfont.
(As with different snippets on this article, this :root block is a partial excerpt the ultimate stylesheet combines it with further customized properties outlined afterward.)
Fluid typography
Viewport-based fluid sort doesn’t know the way broad its component is. A heading in a slim sidebar can due to this fact scale as if it fills the viewport.
.article-container {
container-type: inline-size;
}
:root {
--step-0: clamp(1rem, 0.9rem + 1cqi, 1.125rem);
--step-3: clamp(2rem, 1.6rem + 3cqi, 2.75rem);
}
container-type: inline-size makes the wrapper a container question, so its youngsters can use cqi: a unit based mostly on the container relatively than the viewport. Changing vw in clamp() lets the identical heading scale appropriately in an article column or a 300px sidebar with out component-specific media queries.
Vertical rhythm: trim the half-leading
:heading, p, blockquote, figcaption, li {
text-box-trim: trim-both;
text-box-edge: cap alphabetic;
/* shorthand: text-box: trim-both cap alphabetic; */
}
line-height contains house above the cap-height and under the baseline: half-leading. It turns into apparent when textual content should align tightly with one other component. text-box-trim removes it, whereas text-box-edge measures from capital-letter caps to the alphabetic baseline. This can be a metrics-aware various to font-specific negative-margin nudges.
@helps selector(:heading) {
:heading {
text-box: trim-both cap alphabetic;
}
}
Selectors Degree 5 proposes :heading, matching h1 to h6 and [role="heading"]. It has not landed in browsers, so maintain it behind an @helps selector() test.
Balanced headlines, orphan-free paragraphs
:heading, blockquote, figcaption {
text-wrap: steadiness;
}
p, li, dd {
text-wrap: fairly;
max-width: 65ch;
}
[contenteditable="true"] {
text-wrap: steady;
}
Default heading wrapping can depart one brief last line. text-wrap: steadiness distributes textual content throughout its strains and works greatest for blocks of as much as about six strains. For paragraphs, text-wrap: fairly avoids a stranded last phrase with out balancing the entire block. Each substitute JavaScript orphan fixers. Editable textual content wants text-wrap: steady, which prevents every new phrase from reshuffling the paragraph.
Hyphenation, correctly tuned
p {
hyphens: auto;
}
hyphens: auto depends on the browser’s language dictionaries, so the doc wants an accurate lang attribute (for instance ). With out one the browser has no dictionary to make use of and gained’t be capable to hyphenate textual content. Even with the proper lang, the defaults can depart tiny phrase fragments or a number of hyphenated strains in a row.
p {
hyphens: auto;
hyphenate-limit-chars: 6 3 3; /* phrase size, min earlier than break, min after break */
hyphenate-character: "‐"; /* use correct hyphen glyph as a substitute of sprint */
hyphenate-limit-lines: 2; /* not more than 2 consecutive hyphenated strains */
hyphenate-limit-zone: 8%; /* scale back total hyphenation frequency */
hyphenate-limit-last: all the time; /* by no means hyphenate the final phrase of a paragraph */
/* Safari legacy equivalents */
-webkit-hyphens: auto;
-webkit-hyphenate-limit-before: 4;
-webkit-hyphenate-limit-after: 3;
-webkit-hyphenate-limit-lines: 2;
}
pre, code, var, samp, kbd, :heading {
hyphens: guide;
-webkit-hyphens: guide;
}
hyphenate-limit-chars controls the smallest breakable phrase and fragments; hyphenate-limit-lines prevents a ladder impact. hyphenate-limit-zone makes breaks much less frequent, whereas hyphenate-limit-last protects the ultimate phrase. Safari wants the prefixed equivalents, so declare each units collectively.
Set code, variables, and headings to hyphens: guide: a damaged code phrase can change that means, and a hyphenated heading often reads as an error.
Hanging punctuation and ink-aware underlines
blockquote, p {
hanging-punctuation: first allow-end final;
}
a {
text-decoration-thickness: from-font;
text-underline-offset: 0.15em;
text-decoration-skip-ink: auto;
}
Punctuation marks don’t carry the identical visible weight as letters, so a citation mark sitting initially of a line can look barely indented in opposition to the textual content under it. hanging-punctuation lets specified punctuation sit fractionally outdoors the textual content block’s edge.
The first worth handles opening punctuation, and final handles closing punctuation on the finish of a block, whereas allow-end lets punctuation hold previous the correct edge relatively than force-wrapping. The result's a textual content block whose edges learn as genuinely flush.
Underlines have a associated drawback. Browsers apply flat, one-size-fits-all thickness and place whatever the font’s precise weight.
text-decoration-thickness: from-font reads the thickness the font’s designer supposed. text-underline-offset offers descenders room to breathe, and text-decoration-skip-ink: auto interrupts the underline wherever a descender crosses it relatively than drawing straight by the letter.
Variable font fine-tuning
physique {
font-optical-sizing: auto;
font-variant-ligatures: common-ligatures contextual;
font-kerning: regular;
}
/* Darkish mode grade compensation */
:root {
--vf-grad: 0;
}
@media (prefers-color-scheme: darkish) {
:root { --vf-grad: -50; }
}
* {
font-variation-settings: "GRAD" var(--vf-grad, 0);
}
Variable fonts usually embrace an optical dimension axis, or opsz. It lets a typeface use thinner strokes at giant show sizes and thicker, extra legible strokes when set small.
font-optical-sizing: auto lets the font make that adjustment based mostly on its precise rendered dimension. The @helps block covers the older font-variation-settings syntax some browsers nonetheless want, whereas font-variant-ligatures and font-kerning allow the font’s personal ligatures and kerning pairs.
Gentle textual content on a darkish background genuinely reads heavier than the identical weight on a lightweight background. The place a font ships a GRAD, or grade, axis, the darkish mode block dials that perceived weight down with out altering glyph form or width the way in which an precise weight change would. Fonts with out a GRAD axis safely ignore the setting.
@font-face {
font-family: "Mono";
src: url("/fonts/mono-var.woff2") format("woff2");
size-adjust: 105%; /* tune per-font */
}
Mixing a monospace font into working prose usually exposes a mismatched x-height on the similar declared font-size. The size-adjust key phrase, set as soon as on the @font-face stage, scales that font’s rendered output particularly, leaving the precise font-size worth untouched.
Numeric, emoji, and script-aware textual content
Numbers in a desk want an identical widths in order that they stack right into a clear column on the display. Conversely, numbers in flowing textual content don’t want the identical remedy and may sit naturally alongside the encircling phrases.
desk, .tabular {
font-variant-numeric: tabular-nums lining-nums;
}
.prose {
font-variant-numeric: oldstyle-nums proportional-nums;
}
.emoji-color {
font-variant-emoji: emoji;
}
.emoji-mono {
font-variant-emoji: textual content;
}
The tabular-nums and lining-nums key phrases give desk digits a hard and fast width and full cap top. oldstyle-nums and proportional-nums let digits sit alongside lowercase letters with out sticking up and disrupting the sentence’s texture.
font-variant-emoji makes the selection between full-colour and monochrome emoji specific. With out it, the browser or platform will get to decide on a fallback, and the consequence can range greater than you anticipate. Apply .emoji-color or .emoji-mono to containers the place you need to implement that selection, relatively than setting it globally on the physique.
Locale-aware quotes and native subcripts and superscripts
Quote fashion and character order differ by language and area. English nests single quotes inside double quotes, British English reverses that conference, and French makes use of guillemets with a skinny house.
:the place(:lang(en)) > * { quotes: """ """ "'" "'"; }
:the place(:lang(en-GB)) > * { quotes: "'" "'" """ """; }
:the place(:lang(fr)) > * { quotes: "« 0202F" " 0202F»" """ """; }
q::earlier than { content material: open-quote; }
q::after { content material: close-quote; }
The quotes property declares the order for every language by :lang(). The native q component then inserts the correct pair by open-quote and close-quote. The identical markup can render accurately in every locale with out hardcoding characters into the content material.
Wrapping the principles in :the place() retains them at zero specificity, so a venture can override the quote fashion with one plain class.
Default sub and sup styling is synthesized. The browser simply shrinks the textual content and shifts the baseline, relatively than utilizing glyphs truly designed for that place.
@helps (font-variant-position: sub) {
:is(sub, .sub) {
vertical-align: baseline;
font-size: 100%;
line-height: inherit;
font-variant-position: sub;
}
}
@helps (font-variant-position: tremendous) {
:is(sup, .sup) {
vertical-align: baseline;
font-size: 100%;
line-height: inherit;
font-variant-position: tremendous;
}
}
font-variant-position switches to the font’s personal purpose-built sub and superscript glyphs the place they exist, resetting vertical-align, font-size, and line-height in order that they don’t battle in opposition to the actual glyph’s supposed dimension and place. Most typefaces solely embrace correct numerals relatively than letters, so test the particular font earlier than making use of this broadly.
A observe on specificity: :is() and :the place()
:the place() all the time resolves to zero specificity, which is helpful for a reset. Each rule right here is an opinionated default, and wrapping it in :the place() lets a venture override a property with a plain class.
:the place(h1, h2, h3, h4, h5, h6) {
margin-block: 0.5em;
}
:is() solves a unique drawback: repetition. The heading selector record seems a number of instances in a typographic stylesheet, and grouping it avoids the danger of forgetting a stage.
:is(h1, h2, h3, h4, h5, h6), p, blockquote, figcaption, li {
text-box: trim-both cap alphabetic;
}
In contrast to :the place(), :is() carries the specificity of its most particular argument.
The ultimate tidy-typography.css
Right here’s the entire reset, combining the steady layers. Unsupported declarations are ignored by the browser, so the stylesheet may be adopted progressively.
/* ==========================================================================
tidy-typography.css — a contemporary typography reset
Each rule degrades gracefully in unsupported browsers.
========================================================================== */
html {
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-synthesis: none;
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
physique {
max-width: 800px;
margin: 0 auto;
padding: 1rem;
}
/* ---- Fluid typography ---- */
.article-container {
container-type: inline-size;
}
:root {
--step-0: clamp(1rem, 0.9rem + 1cqi, 1.125rem);
--step-1: clamp(1.25rem, 1.1rem + 1.5cqi, 1.5rem);
--step-2: clamp(1.6rem, 1.35rem + 2cqi, 2rem);
--step-3: clamp(2rem, 1.6rem + 3cqi, 2.75rem);
--step-4: clamp(2.5rem, 1.9rem + 4.5cqi, 4rem);
--vf-grad: 0;
}
@media (prefers-color-scheme: darkish) {
:root {
--vf-grad: -50;
}
}
/* ---- Variable font fine-tuning ---- */
* {
font-variation-settings: "GRAD" var(--vf-grad, 0);
}
physique {
font-size: var(--step-0);
font-size-adjust: from-font;
line-height: 1.6;
font-optical-sizing: auto;
font-variant-ligatures: common-ligatures contextual;
font-kerning: regular;
}
/* ---- Textual content field trim ---- */
:is(h1, h2, h3, h4, h5, h6), p, blockquote, figcaption, li {
text-box: trim-both cap alphabetic;
}
/* ---- Textual content wrap ---- */
:is(h1, h2, h3, h4, h5, h6), blockquote, figcaption {
text-wrap: steadiness;
font-size: var(--step-3);
line-height: 1.15;
letter-spacing: -0.01em;
}
h1 {
font-size: var(--step-4);
}
p, li, dd {
text-wrap: fairly;
max-width: 65ch;
hyphens: auto;
hyphenate-limit-chars: 6 3 3;
hyphenate-limit-lines: 2;
hyphenate-limit-zone: 8%;
hyphenate-limit-last: all the time;
}
pre, code, var, samp, kbd, :is(h1, h2, h3, h4, h5, h6) {
hyphens: guide;
}
blockquote, p {
hanging-punctuation: first allow-end final;
}
[contenteditable="true"] {
text-wrap: steady;
}
/* ---- Numeric, emoji, and script-aware textual content ---- */
desk, .tabular {
font-variant-numeric: tabular-nums lining-nums;
}
.prose {
font-variant-numeric: oldstyle-nums proportional-nums;
}
.emoji-color {
font-variant-emoji: emoji;
}
.emoji-mono {
font-variant-emoji: textual content;
}
/* ---- Locale-aware quotes ---- */
:the place(:lang(en)) > * {
quotes: '"' '"' "'" "'";
}
:the place(:lang(en-GB)) > * {
quotes: "'" "'" '"' '"';
}
q::earlier than {
content material: open-quote;
}
q::after {
content material: close-quote;
}
/* ---- Native sub and superscripts ---- */
@helps (font-variant-position: sub) {
sub, .sub {
vertical-align: baseline;
font-size: 100%;
line-height: inherit;
font-variant-position: sub;
}
}
@helps (font-variant-position: tremendous) {
sup, .sup {
vertical-align: baseline;
font-size: 100%;
line-height: inherit;
font-variant-position: tremendous;
}
}
/* ---- Ink-aware underlines ---- */
a {
text-decoration-thickness: from-font;
text-underline-offset: 0.15em;
text-decoration-skip-ink: auto;
}
What belongs in a reset
A lot of this CSS removes work that used to leak into different elements of a construct. text-wrap can take over orphan dealing with from JavaScript, container question models maintain a part’s sort scale tied to its personal width, and text-box-trim avoids the damaging margins as soon as used to compensate for main. quotes, font-variant-position, and font-optical-sizing hand a number of smaller particulars again to the language and the font as a substitute of a group of one-off fixes.
Browser help will proceed to vary, particularly for text-wrap and text-box-trim. Unsupported declarations are ignored, however that solely helps when the unenhanced typography is suitable in its personal proper.
A few of these guidelines are nonetheless too new for a baseline reset. A information article, a advertising and marketing web page, and an editable product interface don't ask the identical issues of their textual content; the set that belongs in yours will depend on the font, the content material, and the browsers you continue to must help.







