Typography

Foundations

All font sizes output in rem, converted from px against $base-font-size (16px). No root font-size is written to html for the default 16px โ€” an author font-size there would override the reader's browser default-size setting and pin every rem in the stylesheet, which is the one thing rem output exists to avoid. Set $base-font-size to something else and the baseline moves as a percentage, which still tracks that setting. See Configuration.

Letter-spacing is the exception: px values convert to em, against that rule's own font-size rather than the root. Tracking corrects a size, so it has to move when the size does โ€” .supertitle is 2px at 14px, and it stays that ratio under .fs-32 instead of staying 2px.

Default fonts:

Variable Default
$heading-font 'Roboto', sans-serif
$paragraph-font 'Nunito', sans-serif
$mono-font monospace

body uses $paragraph-font. h1โ€“h6 elements override to $heading-font.

Default line-heights:

Variable Value
$line-height 1.6 (html root) โ€” $line-heights.normal
$heading-line-height 1.25 โ€” $line-heights.tight
$paragraph-line-height 1.6 โ€” $line-heights.normal

All three read out of the $line-heights map rather than carrying their own number, so retuning the map moves the type scale and the .lh-* utilities together. Override a variable directly to break one out of the map.


Type scale

Headings

h1โ€“h3 have two size sets: mobile (default, no media query) and desktop (min-width: 1024px, the lg breakpoint). h4โ€“h6 have one โ€” at 20px and below there is nothing to give back on a small screen. The semantic tag and its class equivalent share identical rules โ€” .h1 gives heading styling without the <h1> element.

Each heading entry: (font-size, letter-spacing, line-height, font-weight). Sizes output as rem, letter-spacing as em.

Selector Desktop size Mobile size line-height font-weight
h1, .h1 3rem (48px) 2rem (32px) 1.25 bold
h2, .h2 2rem (32px) 1.5rem (24px) 1.25 bold
h3, .h3 1.5rem (24px) 1.25rem (20px) 1.25 bold
h4, .h4 1.25rem (20px) โ€” 1.25 bold
h5, .h5 1rem (16px) โ€” 1.25 bold
h6, .h6 0.875rem (14px) โ€” 1.25 bold

This is a reading ladder, not a poster one: it opens a document rather than a hero. h5 sits at body size and h6 below it, carrying their level through weight and family instead of scale โ€” the same collapse GitHub's and most docs renderers' prose use. For display type, reach for .fs-64 / .fs-96, which are still in $font-sizes.

No letter-spacing is set on any heading or paragraph. Tracking corrects a specific face at a specific size; applied blind across a scale it fights whatever $paragraph-font is actually set to. .supertitle is the exception, since capitals are drawn to be set apart.

Heading elements also carry default bottom margins (applied to the tag, not the class):

Element margin-bottom
h1 0.33em
h2 0.42em
h3 0.50em
h4 0.65em
h5 0.80em
h6 1.00em

Example โ€” same visual output, different semantics:

<h2>Section title</h2>
<div class="h2">Section title</div>

Paragraph and body text

Non-heading entries use a single size set (no responsive switch). letter-spacing values in px are converted to em against the same entry's font-size.

Selector font-size letter-spacing line-height
.p1 1.5rem (24px) โ€” 1.6
.p2 1.25rem (20px) โ€” 1.6
p 1rem (16px) โ€” 1.6
.p3, figcaption 0.875rem (14px) โ€” 1.6
.p4, small 0.75rem (12px) โ€” 1.6
.supertitle 0.875rem (14px) 0.1428571429em (2px at its own 14px) 1.6

.supertitle additionally sets font-weight: 500 and text-transform: uppercase.

<p class="p1">Large body copy</p>
<p class="p2">Medium body copy</p>
<p>Default paragraph โ€” 1rem / 16px</p>
<figcaption>Caption text (same as .p3)</figcaption>
<small>Fine print (same as .p4)</small>
<p class="supertitle">Label / eyebrow text</p>

Font size utilities โ€” .fs-*

Generated from $font-sizes. px in the class name, rem in the output โ€” same convention as .pt-32, same toRem() conversion the type scale uses, so .fs-24 and .p1 land on exactly the same size.

Class font-size Matches
.fs-12 0.75rem (12px) .p4, small
.fs-14 0.875rem (14px) .p3, figcaption
.fs-16 1rem (16px) p
.fs-20 1.25rem (20px) .p2, h4
.fs-24 1.5rem (24px) .p1, h3 desktop
.fs-32 2rem (32px) h2 desktop
.fs-48 3rem (48px) h1 desktop
.fs-64 4rem (64px) display only
.fs-96 6rem (96px) display only

This is the nudge that does not need a new component class โ€” the type scale still owns letter-spacing, line-height and weight, .fs-* changes size only. Responsive variants included:

<h2 class="fs-32 fs-md-64">Smaller on phones</h2>

Line height utilities โ€” .lh-*

Generated from $line-heights. Unitless values on purpose: a unitless line-height inherits as a ratio, so children scale with their own font-size; a px one inherits as a frozen box and collapses nested type.

Class line-height
.lh-1 1
.lh-tight 1.25 โ€” headings
.lh-normal 1.6 โ€” body copy, html root
.lh-loose 1.75

tight and normal are not only utility values: $heading-line-height and $paragraph-line-height read them out of this map, so a change here moves the type scale with the classes.

Pairs with .fs-* โ€” bumping size usually means loosening or tightening leading:

<p class="fs-32 lh-tight">Display text needs less leading than body copy.</p>

Font family utilities

Override the font family on any element. All use !important.

Class CSS
.font-heading font-family: 'Roboto', sans-serif
.font-paragraph font-family: 'Nunito', sans-serif
.font-mono font-family: monospace

Font weight utilities

Weight lives under font-, matching the CSS property. All use !important.

Class font-weight
.font-thin 100
.font-ultra-light 200
.font-light 300
.font-normal 400
.font-medium 500
.font-semi-bold 600
.font-bold 700
.font-ultra-bold 800
.font-black 900

Warning

The .text-* weight aliases (.text-bold, .text-heavy, โ€ฆ) were removed in 3.0.0. text-* is reserved for real text- properties โ€” text-align, text-transform, text-decoration. Rename to the font-* form; .text-heavy becomes .font-black.

The relative lighter / bolder classes are gone too. They resolved against the parent's weight, so the same class produced a different weight depending on where it landed โ€” the one thing a utility class must not do. Pick the numeric class you actually want.


Text alignment

Generated with responsive variants (all breakpoints). Base classes apply at all widths; breakpoint-infixed variants apply at min-width.

.text-left          โ†’ text-align: left
.text-center        โ†’ text-align: center
.text-right         โ†’ text-align: right

Responsive examples:

.text-md-center     โ†’ text-align: center  (min-width: 768px)
.text-lg-left       โ†’ text-align: left    (min-width: 1024px)

Text decoration

.text-underline          โ†’ text-decoration: underline
.text-overline           โ†’ text-decoration: overline
.text-line-through       โ†’ text-decoration: line-through
.text-decoration-none    โ†’ text-decoration: none

Responsive variants follow the same breakpoint-infix pattern as alignment.


Text transform

.text-uppercase          โ†’ text-transform: uppercase
.text-lowercase          โ†’ text-transform: lowercase
.text-capitalize         โ†’ text-transform: capitalize
.text-full-width         โ†’ text-transform: full-width
.text-full-size-kana     โ†’ text-transform: full-size-kana
.text-transform-none     โ†’ text-transform: none

Responsive variants available for all breakpoints.

Note

full-width and full-size-kana are CJK typographic values โ€” full-width converts halfwidth latin and kana to their fullwidth forms, full-size-kana converts small kana to full-size (a ruby-text convention). Both are no-ops on latin-only text.


Font style

.font-italic     โ†’ font-style: italic

.text-italic was removed in 3.0.0 โ€” font-style is a font- property, so it belongs in the font-* namespace with the weights.


Whitespace

.text-nowrap            โ†’ white-space: nowrap

Text wrapping

.text-balance           โ†’ text-wrap: balance
.text-pretty            โ†’ text-wrap: pretty

balance evens out line lengths across a block โ€” the fix for a heading whose last line is one orphaned word. Browsers cap it at a handful of lines (4โ€“6), so it is a headline tool, not a body-copy one. pretty is the body-copy counterpart: it only prevents orphans, with no line limit.

Not responsive, and there is no text-wrap: nowrap class โ€” .text-nowrap above is the white-space one, and two classes a character apart is a trap.

Truncation

.truncate               โ†’ overflow: hidden; white-space: nowrap; text-overflow: ellipsis

Single-line ellipsis. The element needs a width to overflow against โ€” a block element inherits one from its parent, but a flex or grid child does not shrink below its content by default, so pair it with .min-w-0 there:

<div class="d-flex">
  <span class="truncate min-w-0">A very long label that gets cut offโ€ฆ</span>
</div>

For more than one line, use .line-clamp-{n} โ€” generated from $line-clamps (1, 2, 3, 4, 5, 6), not responsive.

.line-clamp-3           โ†’ display: -webkit-box; -webkit-box-orient: vertical;
                          -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden

The clamp is implemented with display: -webkit-box, which is still the only form every supported browser understands โ€” the unprefixed line-clamp is emitted alongside it for the ones that do. Two consequences:

  • display is part of the mechanism, so putting .d-block or .d-flex on the same element switches the clamp off. Clamp the inner element instead.
  • There is no .line-clamp-none. Removing the class is the way to unclamp; if you need to undo it at a breakpoint, swap in a .d-* class there.

List utilities

From _list.scss. Strips default list styling.

ul.reset,
ol.reset {
  list-style: none;
  margin: 0;
  padding: 0;
  text-indent: 0;
}

The .reset class must be applied directly on the <ul> or <ol> element (qualified selector).

<ul class="reset">
  <li>No bullets, no indent</li>
</ul>