├── Inconsolata.woff2 ├── RobotoFlex.woff2 ├── .gitattributes ├── Literata-var.woff2 ├── bookmania-bold.woff2 ├── Literata-Italic-var.woff2 ├── bookmania-blackitalic.woff2 ├── tods.css ├── tods.html └── README.md /Inconsolata.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clagnut/TODS/HEAD/Inconsolata.woff2 -------------------------------------------------------------------------------- /RobotoFlex.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clagnut/TODS/HEAD/RobotoFlex.woff2 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Literata-var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clagnut/TODS/HEAD/Literata-var.woff2 -------------------------------------------------------------------------------- /bookmania-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clagnut/TODS/HEAD/bookmania-bold.woff2 -------------------------------------------------------------------------------- /Literata-Italic-var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clagnut/TODS/HEAD/Literata-Italic-var.woff2 -------------------------------------------------------------------------------- /bookmania-blackitalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clagnut/TODS/HEAD/bookmania-blackitalic.woff2 -------------------------------------------------------------------------------- /tods.css: -------------------------------------------------------------------------------- 1 | /* Typographic and Opentype Default Stylesheet [TODS]. For details and credits see Github. */ 2 | 3 | 4 | /* 5 | 1. Reset 6 | */ 7 | 8 | html { 9 | -moz-text-size-adjust: none; 10 | -webkit-text-size-adjust: none; 11 | text-size-adjust: none; 12 | } 13 | 14 | body, h1, h2, h3, h4, h5, h6, address, p, hr, pre, blockquote, ol, ul, li, dl, dt, dd, figure, figcaption, div, table, caption, form, fieldset { 15 | margin: 0; 16 | } 17 | 18 | input, 19 | button, 20 | textarea, 21 | select { 22 | font-family: inherit; 23 | font-size: inherit; 24 | } 25 | 26 | 27 | /* 28 | 2. Web fonts 29 | */ 30 | 31 | @font-face { 32 | font-family: 'Literata'; 33 | src: url('Literata-var.woff2') format('woff2') tech(variations), 34 | url('Literata-var.woff2') format('woff2-variations'); 35 | font-weight: 1 1000; 36 | font-stretch: 50% 200%; 37 | font-style: normal; 38 | font-display: fallback; 39 | } 40 | 41 | @font-face { 42 | font-family: 'Literata'; 43 | src: url('Literata-Italic-var.woff2') format('woff2') tech(variations), 44 | url('Literata-Italic-var.woff2') format('woff2-variations'); 45 | font-weight: 1 1000; 46 | font-stretch: 50% 200%; 47 | font-style: italic; 48 | font-display: swap; 49 | } 50 | 51 | @font-face { 52 | font-family: 'Inconsolata'; 53 | src: url('Inconsolata.woff2') format('woff2') tech(variations), 54 | url('Inconsolata.woff2') format('woff2-variations'); 55 | font-weight: 1 1000; 56 | font-stretch: 50% 200%; 57 | font-style: normal; 58 | font-display: fallback; 59 | size-adjust:105%; 60 | } 61 | 62 | 63 | /* 64 | 3. Global defaults 65 | */ 66 | 67 | body { 68 | line-height: 1.5; 69 | text-decoration-skip-ink: auto; 70 | font-optical-sizing: auto; 71 | font-variant-ligatures: common-ligatures no-discretionary-ligatures no-historical-ligatures contextual; 72 | font-kerning: normal; 73 | } 74 | 75 | button, input, label { 76 | line-height: 1.1; 77 | } 78 | 79 | 80 | /* 81 | 4. Block spacing 82 | */ 83 | 84 | .flow > * + * { 85 | margin-block-start: var(--flow-space, 1em); 86 | } 87 | 88 | .prose { 89 | --flow-space: 1.5em; 90 | } 91 | 92 | 93 | /* 94 | 5. OpenType utility classes 95 | */ 96 | 97 | .dlig { font-variant-ligatures: discretionary-ligatures; } 98 | .hlig { font-variant-ligatures: historical-ligatures; } 99 | .dlig.hlig { font-variant-ligatures: discretionary-ligatures historical-ligatures; } 100 | .pnum { font-variant-numeric: proportional-nums; } 101 | .tnum { font-variant-numeric: tabular-nums; } 102 | .lnum { font-variant-numeric: lining-nums; } 103 | .onum { font-variant-numeric: oldstyle-nums; } 104 | .zero { font-variant-numeric: slashed-zero; } 105 | .pnum.zero { font-variant-numeric: proportional-nums slashed-zero; } 106 | .tnum.zero { font-variant-numeric: tabular-nums slashed-zero; } 107 | .onum.zero { font-variant-numeric: oldstyle-nums slashed-zero; } 108 | .lnum.zero { font-variant-numeric: lining-nums slashed-zero; } 109 | .tnum.lnum.zero { font-variant-numeric: tabular-nums lining-nums slashed-zero; } 110 | .frac { font-variant-numeric: diagonal-fractions; } 111 | .afrc { font-variant-numeric: stacked-fractions; } 112 | .ordn { font-variant-numeric: ordinal; } 113 | .smcp { font-variant-caps: small-caps; } 114 | .hist { font-variant-alternates: historical-forms; } 115 | 116 | @font-feature-values "Fancy Font Name" { /* Match font-family webfont name */ 117 | /* All features are font-specific. The names 'cursive', 'swoopy', etc are user-defined. */ 118 | @styleset { cursive: 1; swoopy: 7 16; } 119 | @character-variant { ampersand: 1; capital-q: 2; } 120 | @stylistic { two-story-g: 1; straight-y: 2; } 121 | @swash { swishy: 1; flowing: 2; } 122 | @ornaments { clover: 1; fleuron: 2; } 123 | @annotation { circled: 1; boxed: 2; } 124 | } 125 | 126 | .ss01 { font-variant-alternates: styleset(cursive); } 127 | .ss02 { font-variant-alternates: styleset(swoopy); } 128 | 129 | .cv01 { font-variant-alternates: character-variant(ampersand); } 130 | .cv02 { font-variant-alternates: character-variant(capital-q); } 131 | 132 | .salt1 { font-variant-alternates: stylistic(two-story-g); } 133 | .salt2 { font-variant-alternates: stylistic(straight-y); } 134 | 135 | .swsh1 { font-variant-alternates: swash(swishy); } 136 | .swsh2 { font-variant-alternates: swash(flowing); } 137 | 138 | .ornm1 { font-variant-alternates: ornaments(clover); } 139 | .ornm2 { font-variant-alternates: ornaments(fleuron); } 140 | 141 | .nalt1 { font-variant-alternates: annotation(circled); } 142 | .nalt2 { font-variant-alternates: annotation(boxed); } 143 | 144 | :root { 145 | --opentype-case: off; 146 | --opentype-sinf: off; 147 | } 148 | .case { --opentype-case: on; } 149 | .sinf { --opentype-sinf: on; } 150 | 151 | * { 152 | font-feature-settings: "case" var(--opentype-case, off), "sinf" var(--opentype-sinf, off); 153 | } 154 | 155 | 156 | /* 157 | 6. Generic helper classes 158 | */ 159 | 160 | .centered { 161 | text-align: center; 162 | text-wrap: balance; 163 | } 164 | 165 | .uppercase { 166 | text-transform: uppercase; 167 | --opentype-case: on; 168 | } 169 | 170 | .smallcaps { 171 | font-variant-caps: all-small-caps; 172 | font-variant-numeric: oldstyle-nums; 173 | } 174 | 175 | 176 | /* 177 | 7. Prose styling defaults 178 | */ 179 | 180 | .prose { 181 | text-wrap: pretty; 182 | font-variant-numeric: oldstyle-nums proportional-nums; 183 | font-size-adjust: 0.507; 184 | } 185 | 186 | strong, b, th { 187 | font-weight: bold; 188 | font-size-adjust: 0.514; /* Check for the different weights you may be using */ 189 | } 190 | 191 | 192 | /* 193 | 8. Headings 194 | */ 195 | 196 | h1, h2, h3, h4 { 197 | line-height: 1.1; 198 | font-size-adjust: 0.514; 199 | font-variant-numeric: lining-nums; 200 | } 201 | 202 | h1 { 203 | font-variant-ligatures: discretionary-ligatures; 204 | font-size-adjust: 0.521; /* check if this changes with an optical sizing axis */ 205 | } 206 | 207 | h1.uppercase { 208 | font-variant-caps: titling-caps; 209 | } 210 | 211 | /* 212 | 9. Sup and sub 213 | */ 214 | 215 | @supports ( font-variant-position: sub ) { 216 | sub, .sub { 217 | vertical-align: baseline; 218 | font-size: 100%; 219 | line-height: inherit; 220 | font-variant-position: sub; 221 | } 222 | } 223 | @supports ( font-variant-position: super ) { 224 | sup, .sup { 225 | vertical-align: baseline; 226 | font-size: 100%; 227 | line-height: inherit; 228 | font-variant-position: super; 229 | } 230 | } 231 | 232 | .chemical { 233 | --opentype-sinf: on; 234 | } 235 | 236 | /* 237 | 10. Tables, times and maths 238 | */ 239 | 240 | td, math, time[datetime*=":"] { 241 | font-variant-numeric: tabular-nums lining-nums slashed-zero; 242 | } 243 | 244 | 245 | /* 246 | 11. Quotes 247 | */ 248 | 249 | /* :lang(en) > * { quotes: '“' '”' '‘' '’' ; } /* “Generic English ‘style’” */ 250 | :lang(fr) > * { quotes: '«\00202F' '\00202F»' '“' '”'; } /* « French “style” » */ 251 | :lang(en-GB) > * { quotes: '‘' '’' '“' '”' ; } /* ‘British “style”’ */ 252 | 253 | q::before { content: open-quote } 254 | q::after { content: close-quote } 255 | 256 | .quoted p:first-of-type::before { 257 | content: '“'; 258 | } 259 | .quoted p:last-of-type::after { 260 | content: '”'; 261 | } 262 | 263 | .quoted p:first-of-type::before { 264 | margin-inline-start: -0.87ch; 265 | } 266 | .quoted p { 267 | hanging-punctuation: first last; 268 | } 269 | @supports(hanging-punctuation: first last) { 270 | .quoted p:first-of-type::before { 271 | margin-inline-start: 0; 272 | } 273 | } 274 | 275 | 276 | /* 277 | 12. Hyphenation 278 | Remember to set lang 279 | */ 280 | 281 | .prose { 282 | -webkit-hyphens: auto; 283 | -webkit-hyphenate-limit-before: 4; 284 | -webkit-hyphenate-limit-after: 3; 285 | -webkit-hyphenate-limit-lines: 2; 286 | 287 | hyphens: auto; 288 | hyphenate-limit-chars: 7 4 3; 289 | hyphenate-limit-lines: 2; 290 | hyphenate-limit-zone: 8%; 291 | hyphenate-limit-last: always; 292 | } 293 | 294 | .prose pre, .prose code, .prose var, .prose samp, .prose kbd, 295 | .prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 { 296 | -webkit-hyphens: manual; 297 | hyphens: manual; 298 | } 299 | 300 | 301 | /* 302 | 13. Dark mode and inverted text 303 | */ 304 | 305 | :root { 306 | --vf-grad: 0; 307 | } 308 | 309 | @media (prefers-color-scheme: dark) { 310 | :root { 311 | --vf-grad: -100; /* Grade is font-specific. */ 312 | } 313 | } 314 | 315 | .inverted { 316 | --vf-grad: -100; 317 | } 318 | 319 | * { 320 | font-variation-settings: "GRAD" var(--vf-grad, 0); 321 | } 322 | -------------------------------------------------------------------------------- /tods.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Typographic and Opentype Default Stylesheet (TODS) 6 | 7 | 8 | 267 | 268 | 269 |
270 |
271 | 272 | 273 |
274 |
275 | 276 |
277 |

Typographic and Opentype Default Stylesheet (TODS)

278 | 279 |

The concept is that TODS sets sensible typographic defaults for use on prose (a column of text like this one). For full details see the readme in the Github repo. The page you’re reading uses fairly random bits of text to demonstrate most aspects of TODS, which you can toggle on and off.

280 | 281 |

This page could be much better and is missing some stuff, such as dark mode. Feel free to branch and edit.

282 | 283 |

1440 ‒ 1500, the Incunabula

284 |

The word incunabula comes from the Latin cunae, meaning cradle. It can describe the earliest stages in the development of anything, but it has come to stand particularly for those books produced before 1500. The meagre information about Gutenberg and his works at the time of his death has not been substantially added to since.

285 | 286 |

The 42-line bible

287 | 288 |

By 1448 he had returned to Mainz and had obtained a loan of 150 gulden from a relative there. This was insufficient for his needs since he was again seeking financial backing only two years later. This time he borrowed 800 gulden, at 6 per cent interest, from Johann Fust.

289 | 290 | 291 |

The job of typography

292 | 293 |

The revered type designer Hermann Zapf said that, for designers, typographic design is sometimes misconstrued as a form of private self-expression(2). Typography is not art – it is craft and design with purpose. It is there to perform a service for the reader. Quoting another great designer, Emil Ruder said in 1969(3):

294 | 295 |
296 |

Typography has one plain duty before it and that is to convey information in writing. No argument or consideration can absolve typography from this duty. A printed work which cannot be read becomes a product without purpose.

297 |
298 | 299 |

A website which cannot be read is a product without purpose. Typography’s chief role is to ensure legibility and readability, but that’s not its only job. Typography should invite the reader into the text.

300 | 301 |

Positional numeral systems

302 |

In any standard positional numeral system, a number is conventionally written as (x)y with x as the string of digits and y as its base. For example, comparing values in hexadecimal, decimal, and octal one might write C12=1210=148. Alternatively you could abbreviate like this: Chex=12dec=14oct.

303 | 304 |

Caffeine

305 | 306 |

Caffeine is a central nervous system (CNS) stimulant of the methylxanthine class. Its chemical formula is C10H16N5O13P3, which you should really typeset using scientific inferiors instead of subscripts: C10H16N5O13P3.

307 | 308 |

Populations

309 | 310 |

Here is recent data on some European countries.

311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 |
CountryAreaPopulationGDPCapital
Austria83,8588,169,929339Vienna
Belgium30,52811,007,000410Brussels
Denmark43,0945,564,219271Copenhagen
France547,03066,104,0002,181Paris
Germany357,02180,716,0003,032Berlin
Greece131,95711,123,034176Athens
325 | 326 |

Chicken

327 |

Chicken

328 | 329 |

Huevos

330 |

Huevos

331 | 332 |

The heading above should be made up of contextual ligatures, discretionary ligatures and various swashes.

333 | 334 |

Colophon

335 | 336 |

The fonts used on the page are Literata, Roboto Flex and Bookmania.

337 | 338 |

As of we had 1000 likes.

339 | 340 |

SEND YOUR SPAM TO SPAM@CLEARLEFT.COM (NO DUCK-PICS PLS).

341 | 342 |
343 |
344 |

Heading

345 |

Each opening or closing quote is replaced by one of the strings from the value of quotes

346 |
347 |
348 |

Heading

349 |

Each opening or closing quote is replaced by one of the strings from the value of quotes

350 |
351 |
352 | 353 |
354 | 355 | 356 | 357 | 410 | 411 | 412 | 413 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typography and Opentype Default Stylesheet (TODS) 2 | 3 | This open source project was initiated following a conversation at [CSS Day](https://cssday.nl/2024/speakers#roel) with [Roel Nieskens](https://pixelambacht.nl/) and his [Mildly Opinionated Prose Styles (MOPS)](https://github.com/RoelN/mops). See the original [blog post introducing TODS](https://clagnut.com/blog/2433) for more context. 4 | 5 | The idea is to set sensible typographic defaults for use on prose (a column of text), making particular use of the font features provided by OpenType. The main principle is that it can be used as starting point for all projects, so doesn’t include design-specific aspects such as font choice, type scale or layout (including how you might like to set the line-length). 6 | 7 | Within the styles is mildly opinionated best practice, which will help set suitable styles should you forget. This means you can also use the style sheet as a checklist, even if you don’t want to implement it as-is. 8 | 9 | TODS uses OpenType features extensively and variable font axes where available. It makes full use of the cascade to set sensible defaults high up, with overrides applied further down. It also contains some handy utility classes. 10 | 11 | You can apply the `TODS.css` stylesheet in its entirety, as its full functionality relies on progressive enhancement within both browsers and fonts. Anything that is not supported will safely be ignored. The only possible exceptions to this are sub/superscripts and application of a grade axis in dark mode, as these are font-specific and could behave unexpectedly depending on the capability of the font. 12 | 13 | In order to preview some of the TODS features, you can check out the preview page `tods.html` and toggle `TODS.css` on and off. This needs more work as the text is a bit of a mish-mash of examples and instructions, and it’s missing some of the utility classes and dark mode. But that’s what open source is for… feel free to fork, improve and add back into the repo. 14 | 15 | ## Walkthrough of the `TODS.css` stylesheet 16 | 17 | Table of contents: 18 | 19 | 1. Reset 20 | 2. Web fonts 21 | 3. Global defaults 22 | 4. Block spacing 23 | 5. Opentype utility classes 24 | 6. Generic help classes 25 | 7. Prose styling defaults 26 | 8. Headings 27 | 9. Superscripts and subscripts 28 | 10. Tables and numbers 29 | 11. Quotes 30 | 12. Hyphenation 31 | 13. Dark mode/inverted text 32 | 33 | ### 1. Reset 34 | 35 | Based on Andy Bell’s [more modern CSS reset](https://piccalil.li/blog/a-more-modern-css-reset/). Only the typographic rules in his reset are used here. You might like to apply the other rules too. 36 | 37 | ``` 38 | html { 39 | -moz-text-size-adjust: none; 40 | -webkit-text-size-adjust: none; 41 | text-size-adjust: none; 42 | } 43 | ``` 44 | 45 | Prevent font size inflation when rotating from portrait to landscape. The [best explainer for this is by Kilian](https://kilianvalkhof.com/2022/css-html/your-css-reset-needs-text-size-adjust-probably/). He also explains why we still need those ugly prefixes too. 46 | 47 | ``` 48 | body, h1, h2, h3, h4, h5, h6, address, p, hr, pre, blockquote, ol, ul, li, dl, dt, dd, figure, figcaption, div, table, caption, form, fieldset { 49 | margin: 0; 50 | } 51 | ``` 52 | 53 | Remove default margins in favour of better control in authored CSS. 54 | 55 | ``` 56 | input, 57 | button, 58 | textarea, 59 | select { 60 | font-family: inherit; 61 | font-size: inherit; 62 | } 63 | ``` 64 | 65 | Inherit fonts for inputs and buttons. 66 | 67 | ### 2. Web fonts 68 | 69 | Use modern variable font syntax so that only supporting browsers get the variable font. Others will get generic fallbacks. 70 | 71 | ``` 72 | @font-face { 73 | font-family: 'Literata'; 74 | src: url('/fonts/Literata-var.woff2') format('woff2') tech(variations), 75 | url('/fonts/Literata-var.woff2') format('woff2-variations'); 76 | font-weight: 1 1000; 77 | font-stretch: 50% 200%; 78 | font-style: normal; 79 | font-display: fallback; 80 | } 81 | ``` 82 | 83 | Include full possible weight range to avoid unintended synthesis of variable fonts with a weight axis. Same applies to stretch range for variable fonts with a width axis. 84 | 85 | For main body fonts, use `fallback` for how the browser should behave while the webfont is loading. This gives the font an extremely small block period and a short swap period, providing the best chance for text to render. 86 | 87 | ``` 88 | @font-face { 89 | font-family: 'Literata'; 90 | src: url('/fonts/Literata-Italic-var.woff2') format('woff2') tech(variations), 91 | url('/fonts/Literata-Italic-var.woff2') format('woff2-variations'); 92 | font-weight: 1 1000; 93 | font-stretch: 50% 200%; 94 | font-style: italic; 95 | font-display: swap; 96 | } 97 | ``` 98 | 99 | For italics use `swap` for an extremely small block period and an infinite swap period. This means italics can be synthesised and swapped in once loaded. 100 | 101 | ``` 102 | @font-face { 103 | font-family: 'Plex Sans'; 104 | src: url('/fonts/Plex-Sans-var.woff2') format('woff2') tech(variations), 105 | url('/fonts/Plex-Sans-var.woff2') format('woff2-variations'); 106 | font-weight: 1 1000; 107 | font-stretch: 50% 200%; 108 | font-style: normal; 109 | font-display: fallback; 110 | size-adjust:105%; /* make monospace fonts slightly bigger to match body text. Adjust to suit - you might need to make them smaller */ 111 | } 112 | ``` 113 | 114 | When monospace fonts are used inline with text fonts, they often need tweaking to appear balanced in terms of size. Use `size-adjust` to do this without affecting reported font size and associated units such as `em`. 115 | 116 | ### 3. Global defaults 117 | 118 | Set some sensible defaults that can be used throughout the whole web page. Override these where you need to through the magic of the cascade. 119 | 120 | ``` 121 | body { 122 | line-height: 1.5; 123 | text-decoration-skip-ink: auto; 124 | 125 | font-optical-sizing: auto; 126 | font-variant-ligatures: common-ligatures no-discretionary-ligatures no-historical-ligatures contextual; 127 | font-kerning: normal; 128 | } 129 | ``` 130 | 131 | Set a nice legible line height that gets inherited. The `font-` properties are set too default CSS and OpenType settings, however they are still worth setting specifically just in case. 132 | 133 | ``` 134 | button, input, label { 135 | line-height: 1.1; 136 | } 137 | ``` 138 | 139 | Set shorter line heights on interactive elements. We’ll do the same for headings later on. 140 | 141 | ### 4. Block spacing 142 | 143 | Reinstate block margins we removed in the reset section. We’re setting consistent spacing based on font size on primary elements within ‘flow’ contexts. The entire ‘prose’ area is a flow context, but so might other parts of the page. For more details on the ‘flow’ utility see Andy Bell’s [favourite three lines of CSS](https://piccalil.li/blog/my-favourite-3-lines-of-css). 144 | 145 | ``` 146 | .flow > * + * { 147 | margin-block-start: var(--flow-space, 1em); 148 | } 149 | ``` 150 | 151 | Rule says that every direct sibling child element of `.flow` has `margin-block-start` added to it. The `>` combinator is added to prevent margins being added recursively. 152 | 153 | ``` 154 | .prose { 155 | --flow-space: 1.5em; 156 | } 157 | ``` 158 | 159 | Set generous spacing between primary block elements (in this case it’s the same as the line height). You could also choose a value from a fluid spacing scale, if you are going down the [fluid typography](https://utopia.fyi) route (recommended, but your milage may vary). See [Utopia.fyi](https://utopia.fyi) for more details and a fluid type tool. 160 | 161 | ### 5. OpenType utility classes 162 | 163 | ``` 164 | .dlig { font-variant-ligatures: discretionary-ligatures; } 165 | .hlig { font-variant-ligatures: historical-ligatures; } 166 | .dlig.hlig { font-variant-ligatures: discretionary-ligatures historical-ligatures; } /* Apply both historic and discretionary */ 167 | 168 | .pnum { font-variant-numeric: proportional-nums; } 169 | .tnum { font-variant-numeric: tabular-nums; } 170 | .lnum { font-variant-numeric: lining-nums; } 171 | .onum { font-variant-numeric: oldstyle-nums; } 172 | .zero { font-variant-numeric: slashed-zero; } 173 | .pnum.zero { font-variant-numeric: proportional-nums slashed-zero; } /* Apply slashed zeroes to proportional numerals */ 174 | .tnum.zero { font-variant-numeric: tabular-nums slashed-zero; } 175 | .lnum.zero { font-variant-numeric: lining-nums slashed-zero; } 176 | .onum.zero { font-variant-numeric: oldstyle-nums slashed-zero; } 177 | .tnum.lnum.zero { font-variant-numeric: tabular-nums lining-nums slashed-zero; } 178 | .frac { font-variant-numeric: diagonal-fractions; } 179 | .afrc { font-variant-numeric: stacked-fractions; } 180 | .ordn { font-variant-numeric: ordinal; } 181 | 182 | .smcp { font-variant-caps: small-caps; } 183 | .c2sc { font-variant-caps: unicase; } 184 | .hist { font-variant-alternates: historical-forms; } 185 | ``` 186 | 187 | Helper utilities matching on/off Opentype layout features available through high level CSS properties. 188 | 189 | ``` 190 | @font-feature-values "Fancy Font Name" { /* match font-family webfont name */ 191 | 192 | /* All features are font-specific. */ 193 | @styleset { cursive: 1; swoopy: 7 16; } 194 | @character-variant { ampersand: 1; capital-q: 2; } 195 | @stylistic { two-story-g: 1; straight-y: 2; } 196 | @swash { swishy: 1; flowing: 2; wowzers: 3 } 197 | @ornaments { clover: 1; fleuron: 2; } 198 | @annotation { circled: 1; boxed: 2; } 199 | } 200 | ``` 201 | 202 | Other Opentype features can have multiple glyphs, accessible via an index number defined in the font - these will be explained in documentation that came with your font. These vary between fonts, so you need to set up a new `@font-font-features` rule for each different font, ensuring the font name matches that of the font family. You then give each feature a custom name such as ‘swoopy’. Note that stylesets can be combined, which is why `swoopy` has a space-separated list of indices `7 16`. 203 | 204 | ``` 205 | /* Stylesets */ 206 | .ss01 { font-variant-alternates: styleset(cursive); } 207 | .ss02 { font-variant-alternates: styleset(swoopy); } 208 | 209 | /* Character variants */ 210 | .cv01 { font-variant-alternates: character-variant(ampersand); } 211 | .cv02 { font-variant-alternates: character-variant(capital-q); } 212 | 213 | /* Stylistic alternates */ 214 | .salt1 { font-variant-alternates: stylistic(two-story-g); } 215 | .salt2 { font-variant-alternates: stylistic(straight-y); } 216 | 217 | /* Swashes */ 218 | .swsh1 { font-variant-alternates: swash(swishy); } 219 | .swsh2 { font-variant-alternates: swash(flowing); } 220 | 221 | /* Ornaments */ 222 | .ornm1 { font-variant-alternates: ornaments(clover); } 223 | .ornm2 { font-variant-alternates: ornaments(fleuron); } 224 | 225 | /* Alternative numerals */ 226 | .nalt1 { font-variant-alternates: annotation(circled); } 227 | .nalt2 { font-variant-alternates: annotation(boxed); } 228 | ``` 229 | 230 | Handy utility classes showing how to access the font feature values you set up earlier using the `font-variant-alternates` property. 231 | 232 | ``` 233 | :root { 234 | --opentype-case: off; 235 | --opentype-sinf: off; 236 | } 237 | 238 | /* If class is applied, update custom property */ 239 | .case { 240 | --opentype-case: on; 241 | } 242 | 243 | .sinf { 244 | --opentype-sinf: on; 245 | } 246 | 247 | /* Apply current state of all custom properties, defaulting to off */ 248 | * { 249 | font-feature-settings: "case" var(--opentype-case, off), "sinf" var(--opentype-sinf, off); 250 | } 251 | ``` 252 | 253 | Set custom properties for OpenType features only available through low level `font-feature-settings`. We need this approach because `font-feature-settings` does not inherit in the same way as `font-variant`. See [Roel’s write-up, including how to apply the same methodology to custom variable font axes](https://pixelambacht.nl/2019/fixing-variable-font-inheritance/). 254 | 255 | ### 6. Generic helper classes 256 | 257 | Some utilities to help ensure best typographic practice. 258 | 259 | ``` 260 | .centered { 261 | text-align: center; 262 | text-wrap: balance; 263 | } 264 | ``` 265 | 266 | When centring text you’ll almost always want the text to be ‘balanced’, meaning roughly the same number of characters on each line. 267 | 268 | ``` 269 | .uppercase { 270 | text-transform: uppercase; 271 | --opentype-case: on; 272 | } 273 | ``` 274 | 275 | When fully capitalising text, ensure punctuation designed to be used within caps is turned on where available, using the Opentype ‘case’ feature. 276 | 277 | ``` 278 | .smallcaps { 279 | font-variant-caps: all-small-caps; 280 | font-variant-numeric: oldstyle-nums; 281 | } 282 | ``` 283 | 284 | Transform both upper and lowercase letters to small caps, and use old style-numerals within runs of small caps so they match size-wise. 285 | 286 | ### 7. Prose styling defaults 287 | 288 | Assign a `.prose` class to your running text, that is to say an entire piece of prose such as the full text of an article or blog post. 289 | 290 | ``` 291 | .prose { 292 | text-wrap: pretty; 293 | font-variant-numeric: oldstyle-nums proportional-nums; 294 | font-size-adjust: 0.507; 295 | } 296 | ``` 297 | 298 | Firstly we get ourselves better widow/orphan control, aiming for blocks of text to not end with a line containing a word on its own. Also we use proportional old-style numerals in running text. 299 | 300 | Also adjust the size of fallback fonts to match the webfont to maintain legibility with fallback fonts and reduce visible reflowing. The `font-size-adjust` number is the aspect ratio of the webfont, which you can [calculate using this tool](https://clagnut.com/sandbox/font-size-adjust-ex.html) (Chrome only). 301 | 302 | ``` 303 | strong, b, th { 304 | font-weight: bold; 305 | font-size-adjust: 0.514; 306 | } 307 | ``` 308 | 309 | Apply a different adjustment to elements which are typically emboldened by default, as bold weights often have a different aspect ratio - check for the different weights you may be using, including numeric semi-bolds (eg. 650). Headings are dealt with separately as the aspect ratio may be affected by optical sizing. 310 | 311 | ### 8. Headings 312 | 313 | ``` 314 | h1, h2, h3, h4 { 315 | line-height: 1.1; 316 | font-size-adjust: 0.514; 317 | font-variant-numeric: lining-nums; } 318 | ``` 319 | 320 | Set shorter line heights on your main headings. Set an aspect ratio for fallback fonts - check for different weights of headings. Use lining numerals in headings, especially when using Title Case. 321 | 322 | ``` 323 | h1 { 324 | font-variant-ligatures: discretionary-ligatures; 325 | font-size-adjust: 0.521; 326 | } 327 | ``` 328 | 329 | Turn on fancy ligatures for main headings. If the font has an optical sizing axis, you might need to adjust the aspect ratio accordingly. 330 | 331 | ``` 332 | h1.uppercase { 333 | font-variant-caps: titling-caps; 334 | } 335 | ``` 336 | 337 | When setting a heading in all caps, use titling capitals which are specially designed for setting caps at larger sizes. 338 | 339 | ### 9. Superscripts and subscripts 340 | 341 | Use proper super- and subscript characters. Apply to `SUB` and `SUP` elements as well as utility classes for when semantic sub/superscripts are not required. 342 | 343 | ``` 344 | @supports ( font-variant-position: sub ) { 345 | sub, .sub { 346 | vertical-align: baseline; 347 | font-size: 100%; 348 | line-height: inherit; 349 | font-variant-position: sub; 350 | } 351 | } 352 | 353 | @supports ( font-variant-position: super ) { 354 | sup, .sup { 355 | vertical-align: baseline; 356 | font-size: 100%; 357 | line-height: inherit; 358 | font-variant-position: super; 359 | } 360 | } 361 | ``` 362 | 363 | If font-variant-position is not specified, browsers will synthesise sub/superscripts, so we need to manually turn off the synthesis. This is the only way to use a font’s proper sub/sup glyphs, however it’s **only safe to use this if** you know your font has glyphs for all the characters you are sub/superscripting. If the font lacks those characters (most only have sub/superscript numbers, not letters), then only Firefox (correctly) synthesises sup and sub - all other browsers will display normal characters in the regular way as we turned the synthesis off. 364 | 365 | ``` 366 | .chemical { 367 | --opentype-sinf: on; 368 | } 369 | ``` 370 | 371 | For chemical formulae like H2O, use scientific inferiors instead of `SUB`. 372 | 373 | ### 10. Tables and numbers 374 | 375 | ``` 376 | td, math, time[datetime*=":"] { 377 | font-variant-numeric: tabular-nums lining-nums slashed-zero; 378 | } 379 | ``` 380 | 381 | Make sure all numbers in tables are lining tabular numerals, adding slashed zeroes for clarity. This could usefully apply where a time is specifically marked up, as well as in mathematics. 382 | 383 | ### 11. Quotes 384 | 385 | Use curly quotes and hang punctuation around blockquotes. 386 | 387 | ``` 388 | :lang(en) > * { quotes: '“' '”' '‘' '’' ; } /* “Generic English ‘style’” */ 389 | :lang(en-GB) > * { quotes: '‘' '’' '“' '”'; } /* ‘British “style”’ */ 390 | :lang(fr) > * { quotes: '«\00202F' '\00202F»' '“' '”'; } /* « French “style” » */ 391 | ``` 392 | 393 | Set punctuation order for inline quotes. Quotes are language-specific, so set a `lang` attribute on your `HTML` element or send the language via a server header. Note the narrow non-breaking spaces encoded in the French example. 394 | 395 | ``` 396 | q::before { content: open-quote } 397 | q::after { content: close-quote } 398 | ``` 399 | 400 | Insert quotes before and after `Q` element content. 401 | 402 | ``` 403 | .quoted, .quoted q { 404 | quotes: '“' '”' '‘' '’'; 405 | } 406 | ``` 407 | 408 | Punctuation order for blockquotes, using a utility class to surround with double-quotes. 409 | 410 | ``` 411 | .quoted p:first-of-type::before { 412 | content: open-quote; 413 | } 414 | .quoted p:last-of-type::after { 415 | content: close-quote; 416 | } 417 | ``` 418 | 419 | Append quotes to the first and last paragraphs in the blockquote. 420 | 421 | ``` 422 | .quoted p:first-of-type::before { 423 | margin-inline-start: -0.87ch; /* Adjust according to font */ 424 | } 425 | .quoted p { 426 | hanging-punctuation: first last; 427 | } 428 | @supports(hanging-punctuation: first last) { 429 | .quoted p:first-of-type::before { 430 | margin-inline-start: 0; 431 | } 432 | } 433 | ``` 434 | 435 | Hang the punctuation outside of the blockquote. Firstly manually hang punctuation with a negative margin, then remove the manual intervention and use `hanging-punctuation` if supported. 436 | 437 | ### 12. Hyphenation 438 | 439 | Turn on hyphenation for prose. Language is required in order for the browser to use the correct hyphenation dictionary. 440 | 441 | ``` 442 | .prose { 443 | -webkit-hyphens: auto; 444 | -webkit-hyphenate-limit-before: 4; 445 | -webkit-hyphenate-limit-after: 3; 446 | -webkit-hyphenate-limit-lines: 2; 447 | 448 | hyphens: auto; 449 | hyphenate-limit-chars: 7 4 3; 450 | hyphenate-limit-lines: 2; 451 | hyphenate-limit-zone: 8%; 452 | hyphenate-limit-last: always; 453 | } 454 | ``` 455 | 456 | Include [additional refinements](https://clagnut.com/blog/2395/) to hyphenation. Respectively, these stop short words being hyphenated, prevent ladders of hyphens, and reduce overall hyphenation a bit. Safari uses legacy properties to achieve some of the same effects, hence the ugly prefixes and slightly different syntax. 457 | 458 | ``` 459 | .prose pre, .prose code, .prose var, .prose samp, .prose kbd, 460 | .prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 { 461 | -webkit-hyphens: manual; 462 | hyphens: manual; 463 | } 464 | ``` 465 | 466 | Turn hyphens off for monospace and headings. 467 | 468 | ### 13. Dark mode/inverted text 469 | 470 | Reduce grade if available to prevent bloom of inverted type. 471 | 472 | ``` 473 | :root { 474 | --vf-grad: 0; 475 | } 476 | 477 | @media (prefers-color-scheme: dark) { 478 | :root { 479 | --vf-grad: -50; 480 | } 481 | } 482 | 483 | * { 484 | font-variation-settings: "GRAD" var(--vf-grad, 0); 485 | } 486 | ``` 487 | 488 | Not all fonts have a grade (`GRAD`) axis, and the grade number is font-specific. We're using the customer property method because `font-variation-settings` provides low-level control meaning each subsequent use of the property completely overrides prior use - the values are not inherited or combined, unlike with `font-variant` for example. --------------------------------------------------------------------------------