Type something to search...
How to set up typography with Tailwind CSS?

How to set up typography with Tailwind CSS?

To set up typography with Tailwind CSS, load your fonts with @font-face in your main CSS file, then register them as design tokens inside an @theme block, for example --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;. In Tailwind CSS v4, every variable in the --font-*, --text-*, --leading-*, --tracking-* and --font-weight-* namespaces automatically becomes a utility class, so --font-display gives you font-display and --text-hero gives you text-hero. For long-form content such as blog posts and documentation, add the official @tailwindcss/typography plugin and wrap the content in a prose class. The rest is applying utilities like text-balance, tabular-nums and underline-offset-4 where they're needed.

Tailwind's defaults are sensible, but most projects need their own fonts, a type scale that matches the design and readable styling for content you don't control, like Markdown or CMS output. Getting those foundations right early saves you from scattering arbitrary values across hundreds of components. In this article you'll learn how to install Tailwind v4 and load custom fonts, how to define font families, sizes, line heights and letter spacing as theme tokens, how to build a fluid scale, how to use the typography plugin and customise it, which text utilities are most useful, and how the older v3 configuration compares.

Installing Tailwind CSS v4

Tailwind CSS v4 moved configuration from a JavaScript file into CSS. If you're starting a new project with Vite, install the core package and its Vite plugin:

npm install tailwindcss @tailwindcss/vite

Add the plugin to your Vite config:

// vite.config.js
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [tailwindcss()],
});

For frameworks that use PostCSS instead, install @tailwindcss/postcss and add it to your PostCSS config:

// postcss.config.mjs
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

Then import Tailwind at the top of your main stylesheet:

/* src/styles/app.css */
@import "tailwindcss";

That single import brings in Preflight (Tailwind's base reset), the default theme and all utilities.

Loading Custom Fonts

Tailwind doesn't load fonts for you. It only creates utilities that reference font families, so the font files must be loaded separately. The most reliable way is to self-host WOFF2 files and declare them with @font-face in the same stylesheet.

Say you've placed variable font files for Inter and Fraunces in public/fonts/:

@import "tailwindcss";

@font-face {
  font-family: "Inter";
  src: url("/fonts/InterVariable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Fraunces";
  src: url("/fonts/Fraunces-Variable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

Both are open-source fonts under the SIL Open Font License, so self-hosting them is permitted. If you use a hosted service like Google Fonts instead, add its link tags to your HTML head and skip the @font-face blocks.

Registering Fonts as Theme Tokens

Now tell Tailwind about the fonts with @theme:

@theme {
  --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-display: "Fraunces", ui-serif, Georgia, serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

Overriding --font-sans changes the default font for the whole site, because Preflight applies the sans family to the html element. --font-display is a new token, and Tailwind generates a font-display utility for it:

<h1 class="font-display text-5xl font-semibold">Fieldnotes</h1>
<p class="font-sans">Body text uses Inter by default.</p>

Always include a fallback stack ending with a generic family. If the web font fails, the browser falls back gracefully instead of using its default serif.

Defining a Type Scale

Tailwind's default font sizes, from text-xs to text-9xl, each come with a paired line height. You can keep them, adjust them or replace them entirely.

Customising Font Sizes and Line Heights

In v4, each size is a --text-* variable, and its default line height is a matching variable with a --line-height suffix:

@theme {
  --text-base: 1.0625rem;
  --text-base--line-height: 1.65;

  --text-lead: 1.25rem;
  --text-lead--line-height: 1.6;

  --text-hero: 3.5rem;
  --text-hero--line-height: 1.05;
  --text-hero--letter-spacing: -0.02em;
}

This changes text-base and adds two new utilities, text-lead and text-hero. The --letter-spacing suffix sets a default tracking for that size, which is useful for large headings that look loose at default spacing.

To replace the whole default scale instead of adding to it, reset the namespace first:

@theme {
  --text-*: initial;

  --text-sm: 0.875rem;
  --text-sm--line-height: 1.5;
  --text-base: 1rem;
  --text-base--line-height: 1.6;
  --text-xl: 1.25rem;
  --text-xl--line-height: 1.4;
  --text-3xl: 1.875rem;
  --text-3xl--line-height: 1.2;
  --text-5xl: 3rem;
  --text-5xl--line-height: 1.1;
}

With --text-*: initial, only the sizes you list are generated. That keeps the scale small and stops designers and developers reaching for in-between sizes.

Overriding Line Height on the Fly

Tailwind supports a slash shorthand to set a size and line height together:

<p class="text-lg/7">Large text with a 1.75rem line height.</p>
<p class="text-sm/relaxed">Small text with a relaxed line height.</p>

The value after the slash can be a spacing multiplier, such as 7, or a named --leading-* token, such as relaxed.

Line Height and Letter Spacing Tokens

Named line heights and letter spacing values live in their own namespaces:

@theme {
  --leading-tight: 1.2;
  --leading-body: 1.65;

  --tracking-tight: -0.015em;
  --tracking-caps: 0.08em;
}
<h2 class="text-3xl leading-tight tracking-tight">Release notes</h2>
<p class="text-xs uppercase tracking-caps">New feature</p>

The tracking-caps token is a good example of why tokens help: all-caps labels almost always need extra letter spacing, and defining it once makes it consistent.

Font Weight Tokens

If your design uses specific weights from a variable font, you can add named weights:

@theme {
  --font-weight-book: 450;
  --font-weight-heading: 650;
}
<p class="font-book">Slightly heavier body text.</p>
<h3 class="font-heading">Section heading</h3>

Building Fluid Type in Tailwind

A theme token can hold any valid CSS value, including clamp(). That makes fluid typography straightforward:

@theme {
  --text-fluid-base: clamp(1rem, 0.9022rem + 0.4348vw, 1.25rem);
  --text-fluid-base--line-height: 1.6;

  --text-fluid-xl: clamp(1.5rem, 1.1087rem + 1.7391vw, 2.5rem);
  --text-fluid-xl--line-height: 1.2;

  --text-fluid-3xl: clamp(2rem, 1.2174rem + 3.4783vw, 4rem);
  --text-fluid-3xl--line-height: 1.05;
}
<h1 class="font-display text-fluid-3xl text-balance">Designing for every screen</h1>
<p class="text-fluid-base">Body text grows gently between phone and desktop sizes.</p>

These values scale between a 360px and a 1280px viewport. If you want to understand the formula behind them, see what fluid typography is and how to build it with clamp().

For a one-off value, an arbitrary value works without touching the theme:

<p class="text-[clamp(1.125rem,1rem+0.5vw,1.375rem)]">One-off fluid size</p>

Use these sparingly. If you find yourself writing the same arbitrary value twice, move it into @theme.

Styling Content with the Typography Plugin

Preflight removes default margins and font sizes from headings, paragraphs and lists. That's ideal for building interfaces, but it means raw HTML from Markdown or a CMS looks unstyled. The official typography plugin restores sensible, well-tuned styles inside a container.

Install it:

npm install -D @tailwindcss/typography

Register it in your CSS:

@import "tailwindcss";
@plugin "@tailwindcss/typography";

Then wrap content in prose:

<article class="prose prose-lg dark:prose-invert">
  <h1>Getting started</h1>
  <p>This article explains how to configure your first project.</p>
  <ul>
    <li>Install the CLI</li>
    <li>Create a project</li>
  </ul>
</article>

The plugin includes:

  • Size modifiers: prose-sm, prose-base, prose-lg, prose-xl and prose-2xl scale all the content proportionally.
  • Colour themes: prose-slate, prose-zinc, prose-neutral and others match Tailwind's grey palettes.
  • Dark mode: dark:prose-invert switches to light text on dark backgrounds.
  • A sensible max width: prose sets max-width: 65ch for comfortable line length. Add max-w-none if your layout already controls width.

Customising prose with Element Modifiers

You can adjust specific elements inside prose straight from your markup:

<article
  class="prose prose-slate
         prose-headings:font-display prose-headings:tracking-tight
         prose-a:text-blue-700 prose-a:underline-offset-4
         prose-code:before:content-none prose-code:after:content-none
         prose-img:rounded-lg"
>
  ...
</article>

The prose-code:before:content-none and after pair removes the backticks the plugin adds around inline code by default, which many people prefer to turn off.

Opting Out Inside prose

If you embed a component, such as a call-to-action card, inside prose content, wrap it in not-prose so it isn't affected by the plugin's styles:

<article class="prose">
  <p>Ready to try it?</p>
  <div class="not-prose rounded-xl bg-slate-900 p-6 text-white">
    <p class="text-lg font-semibold">Start your free trial</p>
  </div>
</article>

Useful Typography Utilities

Tailwind includes utilities for most modern text properties. These are the ones worth knowing:

UtilityCSS it producesUse it for
text-balancetext-wrap: balanceHeadings and captions
text-prettytext-wrap: prettyParagraphs
truncateOverflow hidden, nowrap, ellipsisSingle-line titles
line-clamp-3Three-line clampCard excerpts
tabular-numsfont-variant-numeric: tabular-numsPrices, tables
slashed-zerofont-variant-numeric: slashed-zeroCodes, IDs
hyphens-autohyphens: autoNarrow columns
decoration-2text-decoration-thickness: 2pxLink underlines
underline-offset-4text-underline-offset: 4pxLink underlines
antialiasedGrayscale font smoothing on macOSLight text on dark backgrounds

Here's a card using several of them together:

<article class="max-w-sm rounded-lg border border-slate-200 p-5">
  <p class="text-xs font-semibold uppercase tracking-caps text-slate-500">Case study</p>
  <h3 class="mt-2 font-display text-xl/tight text-balance">
    How a small team halved their support queue in three months
  </h3>
  <p class="mt-3 line-clamp-3 text-sm/6 text-slate-600 text-pretty">
    By rewriting their help articles, adding search to the dashboard and
    tagging every ticket, the team found which questions came up most often
    and fixed the product instead of answering them one by one.
  </p>
  <p class="mt-4 text-sm tabular-nums text-slate-500">Read time: 6 min</p>
</article>

Base Styles for Global Defaults

For styles that should apply everywhere, such as heading balance or link underlines, add them to the base layer rather than repeating utilities on every element:

@layer base {
  h1, h2, h3 {
    text-wrap: balance;
  }

  a {
    text-decoration-thickness: 0.07em;
    text-underline-offset: 0.18em;
  }

  code, kbd, pre {
    font-variant-ligatures: none;
  }
}

Utilities still override these, because Tailwind places utilities in a later cascade layer.

Tailwind v3 Configuration for Comparison

If you're maintaining a project on Tailwind v3, the same setup lives in tailwind.config.js:

// tailwind.config.js (Tailwind CSS v3)
const defaultTheme = require("tailwindcss/defaultTheme");

module.exports = {
  content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
  theme: {
    extend: {
      fontFamily: {
        sans: ["Inter", ...defaultTheme.fontFamily.sans],
        display: ["Fraunces", ...defaultTheme.fontFamily.serif],
      },
      fontSize: {
        hero: ["3.5rem", { lineHeight: "1.05", letterSpacing: "-0.02em" }],
      },
      letterSpacing: {
        caps: "0.08em",
      },
    },
  },
  plugins: [require("@tailwindcss/typography")],
};

The concepts are identical: font families, sizes with paired line heights, and the typography plugin. Only the location of the configuration has changed. When you upgrade, the official npx @tailwindcss/upgrade tool migrates most of this into CSS automatically.


FAQ: Typography with Tailwind CSS

Load the font files with @font-face or a hosted font service, then add a variable such as --font-display inside an @theme block. Tailwind generates a matching font-display utility class automatically.

Override the --font-sans variable in @theme. Preflight applies the sans family to the html element, so the whole site uses your font unless a utility says otherwise.

It comes from the official @tailwindcss/typography plugin and applies well-tuned styles to headings, paragraphs, lists, quotes, code and tables inside the container. It's designed for Markdown and CMS content you can't add classes to.

Yes. Put a clamp() value in a --text-* variable inside @theme to create a reusable fluid size, or use an arbitrary value such as text-[clamp(...)] for a one-off.

Preflight removes the browser's default heading sizes and margins so that you can style them with utilities. Add size utilities to each heading, set base styles in a base layer, or use the prose class for long-form content.

No. Tailwind v4 is configured in CSS with @theme and @plugin. You can still load a JavaScript config with the @config directive if you need it during a migration.


Conclusion

Setting up typography in Tailwind CSS comes down to three steps. Load your fonts with @font-face or a hosted service, register them along with your sizes, line heights, letter spacing and weights as tokens in @theme, and let Tailwind turn those tokens into utilities. Resetting a namespace with initial keeps the scale tight, and clamp() values give you fluid type with no extra tooling.

For content you don't control, the typography plugin and its prose classes handle the heavy lifting, with element modifiers and not-prose for fine control. Add the modern text utilities, such as text-balance, text-pretty, tabular-nums and underline offsets, and put global rules in the base layer. The result is a type system defined in one place, consistent across components and easy to change when the design evolves.

Share :

Related Posts

How to make typography accessible?

How to make typography accessible?

You make typography accessible by choosing clear typefaces, setting text in relative units so it scales with user preferences, giving it enough colou

Dive Deeper
What are the parts of a letterform?

What are the parts of a letterform?

A letterform is the shape of a single letter, and typographers break it down into named parts. The main ones are the stem (the main vertical

Dive Deeper
What are ascenders, descenders and the baseline?

What are ascenders, descenders and the baseline?

The baseline is the invisible line that letters sit on. Ascenders are the parts of lowercase letters that rise above the x-height, such as th

Dive Deeper