Typography
Typography utilities set the size, weight, line height and letter spacing of text, align it, change its case and decoration, and control how it wraps. Sizes, weights and line heights are token scales, so the type on a page can be retuned in one place.
How typography works
Section titled “How typography works”- Token scales.
text-lg,font-boldandleading-tightoutputvar(--nu-font-size-lg),var(--nu-font-weight-bold)andvar(--nu-line-height-tight). - Readable names.
text-{size}sets the size,font-{weight}the weight,leading-{key}the line height andtracking-{key}the letter spacing. - Font family token.
--nu-font-familyholds the configured$font-family. The base styles read it; there is no font family class. - Logical alignment.
text-startandtext-endfollow the text direction;text-leftandtext-rightdo not.
| Prefix | Property | Examples |
|---|---|---|
text-{size} |
font-size |
text-sm, text-2xl |
font-{weight} |
font-weight |
font-medium, font-bold |
leading-{key} |
line-height |
leading-tight, leading-relaxed |
tracking-{key} |
letter-spacing |
tracking-wide |
text-{alignment} |
text-align |
text-start, text-center |
text- also starts the colour classes (text-muted, text-brand). Sizes and colours never share a name. See Colours.
Font size
Section titled “Font size”| Key | Token | Value | Pixels |
|---|---|---|---|
xs | --nu-font-size-xs | 0.75rem | 12px |
sm | --nu-font-size-sm | 0.875rem | 14px |
md | --nu-font-size-md | 1rem | 16px |
base | --nu-font-size-base | 1rem | 16px |
lg | --nu-font-size-lg | 1.25rem | 20px |
xl | --nu-font-size-xl | 1.5rem | 24px |
2xl | --nu-font-size-2xl | 1.75rem | 28px |
3xl | --nu-font-size-3xl | 2rem | 32px |
4xl | --nu-font-size-4xl | 2.5rem | 40px |
<div class="space-y-1"> <div class="text-xs">text-xs</div> <div class="text-sm">text-sm</div> <div class="text-base">text-base</div> <div class="text-lg">text-lg</div> <div class="text-xl">text-xl</div> <div class="text-2xl">text-2xl</div> <div class="text-3xl">text-3xl</div> <div class="text-4xl">text-4xl</div></div>.title { @include apply(font-size(xl));}.hero { @include apply(font-size(clamp(2rem, 5vw, 3rem)));}md and base are both 1rem. base is the size the base styles give h6; md completes the sm, md, lg sequence.
Font weight
Section titled “Font weight”| Key | Token | Value |
|---|---|---|
thin | --nu-font-weight-thin | 100 |
extralight | --nu-font-weight-extralight | 200 |
light | --nu-font-weight-light | 300 |
normal | --nu-font-weight-normal | 400 |
medium | --nu-font-weight-medium | 500 |
semibold | --nu-font-weight-semibold | 600 |
bold | --nu-font-weight-bold | 700 |
extrabold | --nu-font-weight-extrabold | 800 |
black | --nu-font-weight-black | 900 |
<div class="space-y-1 text-lg"> <div class="font-light">font-light</div> <div class="font-normal">font-normal</div> <div class="font-medium">font-medium</div> <div class="font-semibold">font-semibold</div> <div class="font-bold">font-bold</div> <div class="font-black">font-black</div></div>.label { @include apply(font(semibold));}A weight is only visible when the font provides it. A web font shows only the weights you load; the browser fakes a missing bold and falls back to the nearest available weight otherwise.
Line height
Section titled “Line height”| Key | Token | Value |
|---|---|---|
none | --nu-line-height-none | 1 |
tight | --nu-line-height-tight | 1.25 |
snug | --nu-line-height-snug | 1.375 |
normal | --nu-line-height-normal | 1.5 |
relaxed | --nu-line-height-relaxed | 1.625 |
loose | --nu-line-height-loose | 2 |
leading-tight: headings and short labels sit closer together, which suits large text over two or three lines.
leading-relaxed: body text with more room between lines is easier to read in long paragraphs.
<div class="grid cols-2 gap-4"> <p class="leading-tight p-2 bg-alternate rounded">leading-tight: headings and short labels sit closer together, which suits large text over two or three lines.</p> <p class="leading-relaxed p-2 bg-alternate rounded">leading-relaxed: body text with more room between lines is easier to read in long paragraphs.</p></div>.body { @include apply(leading(relaxed));}.caption { @include apply(leading(1.6));}The base styles use normal for body text and tight for headings.
Letter spacing
Section titled “Letter spacing”tracking-* sets letter-spacing in em, so it scales with the font size. Tracking values are plain values, not tokens.
| Class | Value |
|---|---|
tracking-tighter |
-0.05em |
tracking-tight |
-0.025em |
tracking-normal |
0 |
tracking-wide |
0.025em |
tracking-wider |
0.05em |
tracking-widest |
0.1em |
<div class="space-y-1"> <div class="tracking-tight text-2xl font-bold">tracking-tight heading</div> <div class="tracking-widest uppercase text-xs font-semibold text-muted">tracking-widest eyebrow</div></div>.eyebrow { @include apply(tracking(widest), uppercase, font-size(xs));}Alignment
Section titled “Alignment”| Class | Declaration | Follows direction |
|---|---|---|
text-start |
text-align: start |
Yes |
text-end |
text-align: end |
Yes |
text-left |
text-align: left |
No |
text-right |
text-align: right |
No |
text-center |
text-align: center |
|
text-justify |
text-align: justify |
The same markup is rendered twice below. text-start and text-end swap sides in right-to-left text; text-left and text-right stay put.
text-start
text-end
text-left (always left)
text-center
text-start
text-end
text-left (always left)
text-center
<div class="space-y-2"> <p class="text-start p-2 bg-brand-subtle rounded">text-start</p> <p class="text-end p-2 bg-brand-subtle rounded">text-end</p> <p class="text-left p-2 bg-alternate rounded">text-left (always left)</p> <p class="text-center p-2 bg-alternate rounded">text-center</p></div>.label { @include apply(text-start);}.amount { @include apply(text-end);}Use text-start and text-end for interface text, so a right-to-left page needs only dir="rtl". Keep text-left and text-right for content that must stay on one side in every language.
Case, style and decoration
Section titled “Case, style and decoration”| Class | Declaration |
|---|---|
uppercase, lowercase, capitalize |
text-transform |
normal-case |
text-transform: none |
italic, not-italic |
font-style: italic, font-style: normal |
underline, line-through |
text-decoration |
no-underline |
text-decoration: none |
<div class="flex wrap gap-4"> <span class="uppercase">uppercase</span> <span class="capitalize">capitalize each word</span> <span class="italic">italic</span> <span class="underline">underline</span> <span class="line-through text-muted">line-through</span></div>Wrapping and white space
Section titled “Wrapping and white space”| Class | Declaration |
|---|---|
break-normal |
overflow-wrap: normal; word-break: normal |
break-words |
overflow-wrap: break-word |
break-all |
word-break: break-all |
whitespace-normal |
white-space: normal |
whitespace-nowrap |
white-space: nowrap |
whitespace-pre |
white-space: pre |
whitespace-pre-line |
white-space: pre-line |
whitespace-pre-wrap |
white-space: pre-wrap |
break-words: https://example.com/a/very/long/address/without/any/spaces/in/it
whitespace-nowrap keeps this sentence on one line
<div class="grid cols-2 gap-4"> <p class="break-words p-2 bg-alternate rounded">break-words: https://example.com/a/very/long/address/without/any/spaces/in/it</p> <p class="whitespace-nowrap overflow-x-auto p-2 bg-alternate rounded">whitespace-nowrap keeps this sentence on one line</p></div>Wrapping and white space classes have no responsive variants. For one-line and multi-line truncation (truncate, truncate-2 to truncate-5), see Display.
Font family
Section titled “Font family”$font-family is emitted as the --nu-font-family token. The base styles set it on body, headings, buttons and form fields. There is no font family utility class.
| Key | Token | Value |
|---|---|---|
family | --nu-font-family | system-ui, -apple-system, "BlinkMacSystemFont", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", sans-serif |
Use the token wherever you need the configured font in your own CSS, so it changes in one place:
.chart-label { font-family: token(font-family);}To change the font, set $font-family when loading core:
@use "@nuvoui/core" with ( $font-family: ("Inter", system-ui, sans-serif));When the theme feature flag is off, no tokens are emitted and the base styles use the Sass value of $font-family directly.
Responsive variants
Section titled “Responsive variants”Size, weight, line height, letter spacing, alignment, case, style and decoration classes have responsive variants with an @{breakpoint} suffix. Variants apply from their breakpoint upwards.
text-xl text-3xl@md text-center text-start@md
<h3 class="text-xl text-3xl@md text-center text-start@md">text-xl text-3xl@md text-center text-start@md</h3>.page-title { @include apply(font-size(xl), font-size(3xl) md, text-center, text-start md);}| Suffix | Media query |
|---|---|
@xs | @media (min-width: 480px) |
@sm | @media (min-width: 640px) |
@md | @media (min-width: 768px) |
@lg | @media (min-width: 1024px) |
@xl | @media (min-width: 1280px) |
@2xl | @media (min-width: 1536px) |
Typography classes have no state variants. Use a mixin with a state modifier when needed: @include apply(underline hover);.
| Class | Mixin |
|---|---|
text-lg |
font-size(lg) |
font-bold |
font(bold) |
leading-tight |
leading(tight) or leading-tight |
tracking-wide |
tracking(wide) or tracking-wide |
text-start, uppercase, italic, underline, break-words, whitespace-nowrap |
Mixin of the same name |
@use "@nuvoui/core" as *;
.heading { @include apply(font-size(xl), font(bold), leading(tight), font-size(3xl) lg);}.heading { font-size: var(--nu-font-size-xl); font-weight: var(--nu-font-weight-bold); line-height: var(--nu-line-height-tight);}@media (min-width: 1024px) { .heading { font-size: var(--nu-font-size-3xl); }}Keys and custom values
Section titled “Keys and custom values”| Mixin | Accepts |
|---|---|
font-size() |
A $font-sizes key, 0, a value with a unit, calc(), var(), clamp() or a CSS font size keyword (small, larger, inherit) |
font() |
A $font-weights key or a number from 1 to 1000 (font(450) for a variable font) |
leading() |
A $line-heights key, a number or length of 0 or more, calc(), var() or a keyword (normal, inherit) |
tracking() |
A tracking key, 0, a value with a unit, calc(), var() or a CSS-wide keyword |
text-size() was removed in 2.0; use font-size().
Tokens in your own CSS
Section titled “Tokens in your own CSS”.stat { font-size: token(font-size-2xl); font-weight: token(font-weight-semibold); line-height: token(line-height-none);}Configuration
Section titled “Configuration”Pass your own $font-sizes, $font-weights or $line-heights map. Each map replaces the default map; it is not merged. Base element styles read the font size keys sm, base, lg, xl, 2xl, 3xl and 4xl, the weights normal and bold, and the line heights normal and tight; without them core raises an error naming the missing key.
@use "@nuvoui/core" with ( $generate-utility-classes: true, $font-sizes: ("xs": 0.75rem, "sm": 0.875rem, "base": 1rem, "lg": 1.125rem, "xl": 1.25rem, "2xl": 1.5rem, "3xl": 1.875rem, "4xl": 2.25rem));$base-size (default 1rem) sets the html font size in the base styles. Every rem value, so every token on this page, scales with it.
The typography feature flag turns off every class on this page. In 1.x the flag was named config-typo; that key is ignored in 2.0, so rename it.
@use "@nuvoui/core" with ( $generate-utility-classes: true, $feature-flags: ("typography": false));Errors
Section titled “Errors”| Input | Error |
|---|---|
font-size(17) |
font-size: 17 is not a configured key. Valid keys: xs, sm, md, base, lg, xl, 2xl, 3xl, 4xl. Write a unit for a custom value (17px, 17rem). |
font-size(huge) |
font-size(huge): unknown size. Use a key (xs, sm, md, base, lg, xl, 2xl, 3xl, 4xl), a value with a unit, calc(), var() or a keyword (...). |
font(heavy) |
Unknown font weight: heavy. Use a key (thin, extralight, light, normal, medium, semibold, bold, extrabold, black) or a number from 1 to 1000. |
leading(foo) |
Unknown line height: foo. Use a $line-heights key (none, tight, snug, normal, relaxed, loose), a number, a length, calc(), var() or a keyword (...). |
leading(-1) |
leading(-1): line height cannot be negative. |
tracking(2) |
tracking(2): use a key (tighter, tight, normal, wide, wider, widest), 0, a value with a unit, calc() or var(). |
CSS variables
Section titled “CSS variables”| Variable | Description |
|---|---|
--nu-font-family |
The configured $font-family. Read by the base styles. |
--nu-font-size-{key} |
One token per key of $font-sizes. |
--nu-font-weight-{key} |
One token per key of $font-weights. |
--nu-line-height-{key} |
One token per key of $line-heights. |
All typography tokens are fixed across themes. Letter spacing has no tokens.
Class reference
Section titled “Class reference”Generated from the compiled core build. Responsive variants are not listed.
Font size (9 rules)
| Selector | Declarations | Default value |
|---|---|---|
.text-xs | font-size: var(--nu-font-size-xs) | 0.75rem |
.text-sm | font-size: var(--nu-font-size-sm) | 0.875rem |
.text-md | font-size: var(--nu-font-size-md) | 1rem |
.text-base | font-size: var(--nu-font-size-base) | 1rem |
.text-lg | font-size: var(--nu-font-size-lg) | 1.25rem |
.text-xl | font-size: var(--nu-font-size-xl) | 1.5rem |
.text-2xl | font-size: var(--nu-font-size-2xl) | 1.75rem |
.text-3xl | font-size: var(--nu-font-size-3xl) | 2rem |
.text-4xl | font-size: var(--nu-font-size-4xl) | 2.5rem |
Font weight (9 rules)
| Selector | Declarations | Default value |
|---|---|---|
.font-thin | font-weight: var(--nu-font-weight-thin) | 100 |
.font-extralight | font-weight: var(--nu-font-weight-extralight) | 200 |
.font-light | font-weight: var(--nu-font-weight-light) | 300 |
.font-normal | font-weight: var(--nu-font-weight-normal) | 400 |
.font-medium | font-weight: var(--nu-font-weight-medium) | 500 |
.font-semibold | font-weight: var(--nu-font-weight-semibold) | 600 |
.font-bold | font-weight: var(--nu-font-weight-bold) | 700 |
.font-extrabold | font-weight: var(--nu-font-weight-extrabold) | 800 |
.font-black | font-weight: var(--nu-font-weight-black) | 900 |
Line height (6 rules)
| Selector | Declarations | Default value |
|---|---|---|
.leading-none | line-height: var(--nu-line-height-none) | 1 |
.leading-tight | line-height: var(--nu-line-height-tight) | 1.25 |
.leading-snug | line-height: var(--nu-line-height-snug) | 1.375 |
.leading-normal | line-height: var(--nu-line-height-normal) | 1.5 |
.leading-relaxed | line-height: var(--nu-line-height-relaxed) | 1.625 |
.leading-loose | line-height: var(--nu-line-height-loose) | 2 |
Letter spacing (6 rules)
| Selector | Declarations | Default value |
|---|---|---|
.tracking-tighter | letter-spacing: -0.05em | -0.05em |
.tracking-tight | letter-spacing: -0.025em | -0.025em |
.tracking-normal | letter-spacing: 0 | 0 |
.tracking-wide | letter-spacing: 0.025em | 0.025em |
.tracking-wider | letter-spacing: 0.05em | 0.05em |
.tracking-widest | letter-spacing: 0.1em | 0.1em |
Alignment (6 rules)
| Selector | Declarations | Default value |
|---|---|---|
.text-left | text-align: left | left |
.text-center | text-align: center | center |
.text-right | text-align: right | right |
.text-justify | text-align: justify | justify |
.text-start | text-align: start | start |
.text-end | text-align: end | end |
Case, style and decoration (9 rules)
| Selector | Declarations | Default value |
|---|---|---|
.uppercase | text-transform: uppercase | uppercase |
.lowercase | text-transform: lowercase | lowercase |
.capitalize | text-transform: capitalize | capitalize |
.normal-case | text-transform: none | none |
.italic | font-style: italic | italic |
.not-italic | font-style: normal | normal |
.underline | text-decoration: underline | underline |
.line-through | text-decoration: line-through | line-through |
.no-underline | text-decoration: none | none |
Wrapping and white space (8 rules)
| Selector | Declarations | Default value |
|---|---|---|
.break-normal | overflow-wrap: normalword-break: normal | normalnormal |
.break-words | overflow-wrap: break-word | break-word |
.break-all | word-break: break-all | break-all |
.whitespace-normal | white-space: normal | normal |
.whitespace-nowrap | white-space: nowrap | nowrap |
.whitespace-pre | white-space: pre | pre |
.whitespace-pre-line | white-space: pre-line | pre-line |
.whitespace-pre-wrap | white-space: pre-wrap | pre-wrap |
Accessibility and RTL
Section titled “Accessibility and RTL”- Let text resize. Sizes are in
rem, so they follow the user’s browser font size. Avoid fixed pixel sizes in mixins for body text (WCAG 1.4.4 Resize Text). - Line height and spacing. Body text reads best at
leading-normalorleading-relaxed. Layouts must survive users raising line height to 1.5 and letter spacing to 0.12em (WCAG 1.4.12 Text Spacing), so avoid fixed heights on text containers. - Case is presentation.
uppercasechanges how text looks, not what it says; screen readers read the source text. Write the source in normal case. - Weight and contrast. Thin and light weights at small sizes reduce legibility; check contrast at the weight you use.
- RTL. Use
text-startandtext-endfor interface text. Letter spacing breaks joined scripts such as Arabic: avoidtracking-*on text that can be in those languages.