✳️ 681+ shadcn/ui Blocks, lifetime updates, unlimited projects — Get 50+ fresh blocks every month ✳️ 👇
See Components →

Tailwind Typography Plugin

Style Markdown and CMS HTML with prose classes.

by Yucel F. Sahan
5 min read
Updated on

A practical, copy-pasteable guide to @tailwindcss/typography: install it, drop in the prose class, tune sizes and colors, and keep “HTML you don’t control” (Markdown, CMS output) looking clean and consistent.

What the Typography plugin solves

Blog posts, docs, and CMS pages ship a lot of raw HTML: headings, lists, tables, code blocks, images. The Typography plugin adds opinionated styles to that content via a single prose class, so your long-form text looks great without hand-authoring CSS for every tag.

a good typography example from Alfred themea good typography example from Alfred theme

a beautiful blog post using prose classes from Navy theme

Installation

Tailwind v4 (CSS-first)

Add the plugin right in your CSS:

/* app.css (or main.css) */
@import "tailwindcss";
@plugin "@tailwindcss/typography";

That is the new v4 way to enable first-party plugins.

Tailwind v3 (legacy projects)

// tailwind.config.js
module.exports = {
  plugins: [require("@tailwindcss/typography")],
};

This is still fully supported in v3.

Compatibility note: the plugin added explicit support for Tailwind v4 in recent releases. If you upgraded Tailwind, update @tailwindcss/typography too.

Basic usage

Wrap your CMS or Markdown output in .prose:

<article class="prose">
  <h1>Garlic bread with cheese</h1>
  <p>…all your paragraphs, lists, tables, and images go here…</p>
</article>

You can scale the whole type system at breakpoints:

<article class="prose md:prose-lg lg:prose-xl">
  <!-- content -->
</article>

Those size modifiers are built in: prose-sm, prose-base (default), prose-lg, prose-xl, prose-2xl.

Remove the built-in max-width when you need full-bleed content:

<article class="prose max-w-none">
  <!-- content -->
</article>

The plugin ships a comfortable reading width by default; max-w-none opt-out is the sanctioned override.

Color themes and dark mode

Pick a gray theme that matches your brand palette:

<article class="prose prose-slate">…</article>
<!-- also: prose-zinc, prose-neutral, prose-stone -->

Then invert for dark mode:

<article class="prose dark:prose-invert">
  …
</article>

The plugin provides prose-invert for one-line dark color adjustments.

Heads-up: some users report that combining prose-invert with a gray theme like prose-stone can clash under v4. If you hit that, track the issue and either drop the gray theme in dark mode or force priority with !prose-invert.

Target a single element with “element modifiers”

You can keep the global defaults and still tweak any element inline:

<article
  class="prose prose-a:text-indigo-600 prose-a:underline hover:prose-a:text-indigo-500 prose-img:rounded-xl prose-code:before:content-none prose-code:after:content-none"
>
  <!-- links use your brand color, images have rounded corners, code fence quotes removed -->
</article>

Available targets include prose-headings, prose-a, prose-img, prose-code, prose-pre, prose-table, prose-kbd, and many more. In v4, when stacking modifiers like hover, put the non-prose modifier last, as in the example above.

Keep playgrounds and widgets unstyled with not-prose

When you embed UI inside an article (like a code demo or pricing card), fence it off:

<article class="prose">
  <p>Intro text…</p>
  <div class="not-prose">
    <!-- your component here stays untouched by prose styles -->
  </div>
  <p>Back to the article…</p>
</article>

not-prose prevents unwanted inheritance and is the recommended escape hatch.

Customize the defaults (two approaches)

1) Quick, inline utilities with element modifiers

Best for small tweaks that live next to the markup, as shown earlier.

2) Deeper theme overrides (colors, spacing, code blocks, etc.)

If you need to change the generated CSS itself, enable JS config in v4 with @config, then extend the typography theme object:

/* app.css */
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@config "./tailwind.config.js";
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    extend: {
      typography: {
        DEFAULT: {
          css: {
            a: { color: "var(--color-indigo-600)", "&:hover": { color: "var(--color-indigo-500)" } },
            "h1, h2, h3": { scrollMarginTop: "var(--spacing-24)" },
            code: { fontWeight: "600" },
          },
        },
      },
    },
  },
};

That pattern is also how you add a brand color theme, or tune the plugin’s dark tokens.

Rename the prose class (optional)

Prefer wysiwyg or content instead of prose? You can change it when registering the plugin in v4:

@import "tailwindcss";
@plugin "@tailwindcss/typography" { className: wysiwyg; }

Use wysiwyg, wysiwyg-slate, lg:wysiwyg-xl, and not-wysiwyg thereafter.


Common pitfalls and fixes

  • Utilities “don’t work” inside .prose children. The plugin’s element selectors are quite specific, so a bare class="text-red-500" on an h2 might be trumped. Use element modifiers like prose-h2:text-red-500, or apply deeper theme overrides.

  • No styles after upgrading to v4. Ensure you added @plugin "@tailwindcss/typography"; in your CSS and updated the plugin to a v4-compatible release.

Copy-paste starters

Blog article scaffold

<main class="mx-auto max-w-3xl px-4 py-12">
  <article class="prose prose-slate md:prose-lg lg:prose-xl dark:prose-invert">
    <h1>Master Tailwind Typography for Blogs</h1>
    <p class="lead">A clean baseline for Markdown or CMS HTML.</p>

    <h2>Why it matters</h2>
    <p>Readable defaults save hours of CSS tweaks…</p>

    <pre><code>// fenced code block</code></pre>

    <figure>
      <img src="/cover.jpg" alt="Cover" />
      <figcaption>A helpful caption lives here.</figcaption>
    </figure>

    <table>
      <thead><tr><th>Package</th><th>Version</th></tr></thead>
      <tbody><tr><td>@tailwindcss/typography</td><td>latest</td></tr></tbody>
    </table>

    <div class="not-prose my-8">
      <!-- embedded demo or CTA -->
      <button class="px-4 py-2 rounded bg-indigo-600 text-white">Call to Action</button>
    </div>
  </article>
</main>

Minimal v4 CSS to enable the plugin

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

Both snippets follow the official v4 approach and the plugin’s documented API.


TL;DR checklist

  • Add the plugin: @plugin "@tailwindcss/typography"; (v4) or plugins: [require("@tailwindcss/typography")] (v3).

  • Wrap content in .prose; scale with md:prose-lg, lg:prose-xl.

  • Pick a gray theme (prose-slate etc.) and invert in dark mode (dark:prose-invert).

  • Use element modifiers for one-off tweaks (prose-a:*, prose-img:*, prose-code:*).

  • Use not-prose for embedded components; max-w-none to remove the reading-width cap.

  • For deep customization, enable @config and extend theme.typography.

FAQ

How do I install it in Tailwind v4 vs. v3?

In v4, add the plugin in your CSS: @import "tailwindcss"; @plugin "@tailwindcss/typography";. In v3, add require('@tailwindcss/typography') to plugins in tailwind.config.js.

How do I enable dark mode for article content?

Use dark:prose-invert on the same element that has prose. You can also pick a gray theme like prose-slate to match your site’s palette.

How can I stop the plugin from styling an embedded widget or demo?

Wrap the widget in a not-prose container inside your .prose article. Note: you can’t nest a new .prose inside a not-prose block.

Why is my article width capped, and how do I make it full-width?

The plugin ships a comfortable reading max-width. Add max-w-none to the .prose element to let content fill its container.

Use element modifiers like prose-a:text-blue-600, prose-img:rounded-xl, etc. In v4, when stacking with other modifiers (e.g., hover), put the non-prose modifier last. For deeper changes, extend theme.typography via @config.

Yucel F. Sahan

Yucel is a digital product creator and content writer with a knack for full-stack development. He loves blending technical know-how with engaging storytelling to build practical, user-friendly solutions. When he's not coding or writing, you'll likely find him exploring new tech trends or getting inspired by nature.