The Tailwind CSS typography plugin styles HTML you don't control, like Markdown output or CMS content, with a single prose class. In Tailwind CSS v4 you install @tailwindcss/typography and register it in your CSS with @plugin "@tailwindcss/typography";, with no config file. From there, size modifiers, dark:prose-invert and element modifiers like prose-a: handle most customization.
Table of contents
- Why you need the typography plugin
- Installing it in Tailwind v4
- Sizes, gray scales and dark mode
- Element modifiers
- Custom color themes with CSS variables
- Mixing prose with components
- FAQ
Why you need the typography plugin
Tailwind's Preflight resets browser defaults. Headings are the same size as body text, lists have no bullets, and paragraphs have no margins. That's what you want when you're building UI with utility classes, but it's a problem when the HTML comes from a Markdown file, a headless CMS or a rich text editor. You can't add classes to every h2 and li in content you don't write.
The typography plugin fixes this by adding a prose class that styles descendant elements: headings, paragraphs, lists, blockquotes, tables, code, images and more. The defaults are tuned for long-form reading, with a measure of about 65 characters per line and vertical rhythm based on the font size.
Installing it in Tailwind v4
Install the package:
npm install -D @tailwindcss/typography
Then register it in the same CSS file that imports Tailwind:
@import "tailwindcss";
@plugin "@tailwindcss/typography";
That's the whole setup. In v3 you added the plugin to the plugins array in tailwind.config.js; in v4 the @plugin directive replaces that. Now wrap your content:
<article class="prose lg:prose-lg dark:prose-invert mx-auto">
<h1>Release notes</h1>
<p class="lead">A short summary that renders slightly larger than body text.</p>
<p>Rendered Markdown or CMS HTML goes here.</p>
<ul>
<li>Lists get bullets and spacing back</li>
<li>Code, tables and blockquotes are styled too</li>
</ul>
</article>
The lead class is part of the plugin and styles an intro paragraph. In React you'd typically render content with dangerouslySetInnerHTML or a Markdown component inside the prose wrapper.
Renaming the class
If prose conflicts with existing styles, the plugin accepts options directly in CSS:
@plugin "@tailwindcss/typography" {
className: wysiwyg;
}
Every utility then uses the new name: wysiwyg, wysiwyg-lg, wysiwyg-a:underline.
Sizes, gray scales and dark mode
Five size modifiers scale the whole typographic system together, including margins and line heights: prose-sm, prose-base (the default), prose-lg, prose-xl and prose-2xl. Combine them with breakpoints so text grows on larger screens: prose md:prose-lg.
Gray scale modifiers change the text, border and code colors to match one of Tailwind's grays: prose-gray (default), prose-slate, prose-zinc, prose-neutral and prose-stone. Pick the one that matches the rest of your UI. A neutral site with slate-tinted article text looks slightly off.
For dark mode, add dark:prose-invert. It swaps in a lighter set of colors designed for dark backgrounds. If you use a class-based dark mode, define the variant once with @custom-variant dark (&:where(.dark, .dark *)); and dark:prose-invert follows it automatically.
Width
prose sets max-width: 65ch. That's a good default for reading, but inside a layout that already constrains width, add max-w-none and let the parent decide. This is how the article you're reading is set up.
Element modifiers
Element modifiers let you style specific elements inside prose without writing CSS. The syntax is prose-{element}:{utility}:
<article class="prose prose-neutral dark:prose-invert max-w-none
prose-headings:font-semibold prose-headings:tracking-tight
prose-a:text-indigo-600 prose-a:no-underline hover:prose-a:underline
prose-img:rounded-xl
prose-code:rounded prose-code:bg-neutral-100 prose-code:px-1
prose-code:before:content-none prose-code:after:content-none">
...
</article>
Available modifiers include prose-headings, prose-h1 through prose-h6, prose-p, prose-a, prose-lead, prose-blockquote, prose-strong, prose-em, prose-code, prose-pre, prose-ol, prose-ul, prose-li, prose-table, prose-th, prose-td, prose-img, prose-video, prose-figure, prose-figcaption, prose-hr and prose-kbd.
The prose-code:before:content-none prose-code:after:content-none pair is worth remembering. By default the plugin wraps inline code in backtick characters using pseudo-elements; most designs replace that with a background color.
Element modifiers compose with other variants, so dark:prose-a:text-indigo-400 and md:prose-headings:text-4xl both work. If the class list grows past a few lines, it's usually time to move to a custom theme instead.
Code blocks and syntax highlighting
The plugin styles pre blocks with a dark background and light text in both modes, which works well with most syntax highlighters. If you use a highlighter that sets its own background and token colors, such as Shiki or Prism with a theme, the two can clash. Add prose-pre:bg-transparent prose-pre:p-0 and let the highlighter's wrapper control the look, or set prose-pre:bg-neutral-900 to match the theme exactly.
Long lines in code blocks scroll horizontally inside the pre, so the page itself doesn't scroll sideways on phones. If you put the article inside a flex or grid layout, give the column min-w-0; otherwise a wide code block can force the whole column wider than the screen.
Custom color themes with CSS variables
The plugin reads its colors from CSS custom properties like --tw-prose-body, --tw-prose-headings and --tw-prose-links, with matching --tw-prose-invert-* versions for dark mode. In v4 you can define your own theme as a utility and set those variables to your design tokens:
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@theme {
--color-brand-600: oklch(0.51 0.2 265);
--color-brand-300: oklch(0.78 0.11 265);
}
@utility prose-brand {
--tw-prose-links: var(--color-brand-600);
--tw-prose-bullets: var(--color-brand-600);
--tw-prose-quote-borders: var(--color-brand-600);
--tw-prose-invert-links: var(--color-brand-300);
--tw-prose-invert-bullets: var(--color-brand-300);
--tw-prose-invert-quote-borders: var(--color-brand-300);
}
Then use it like the built-in gray themes: prose prose-neutral prose-brand dark:prose-invert. The gray theme sets body and heading colors, and your theme overrides only the accent variables.
If you need deeper changes, such as different heading margins or a custom blockquote style, write regular CSS scoped to .prose in your stylesheet, or load a JavaScript config with @config "./tailwind.config.js"; and use the plugin's typography theme key. The CSS variable approach is simpler and covers the common cases.
Mixing prose with components
Long-form pages often embed things that shouldn't inherit prose styles: a newsletter signup card, a product callout, a code playground. Wrap them in not-prose:
<article class="prose dark:prose-invert">
<p>Regular article text.</p>
<div class="not-prose my-8 rounded-xl border border-neutral-200 p-6 dark:border-neutral-800">
<p class="text-sm font-medium">Get new posts by email</p>
<!-- form styled with normal utilities -->
</div>
<p>More article text.</p>
</article>
Inside not-prose, your utility classes work exactly as they would outside the article. You can't nest prose back inside a not-prose block, so keep embedded components self-contained.
The OpenTailwind blog details blocks show complete article layouts, with headers, featured images and share links around the body, that you can drop a prose container into.
FAQ
Do I still need a tailwind.config.js for the typography plugin?
No. In Tailwind v4, @plugin "@tailwindcss/typography"; in your CSS is enough. A JavaScript config is only needed if you want to customize the plugin's theme through the typography key, and you load it with @config.
Why are my prose styles not showing up?
Check that the @plugin line is in the CSS file that contains @import "tailwindcss", and that the file containing the prose class is being scanned. If your templates live outside the project root, add them with @source "../path";.
How do I remove the backticks around inline code?
Add prose-code:before:content-none prose-code:after:content-none to the prose element, or override .prose code::before and ::after in your CSS.
Can I use prose for UI text like cards and buttons?
You can, but it isn't designed for it. Prose adds margins and font sizes meant for articles. For UI, use regular utilities and reserve prose for content you don't author in HTML.
The typography plugin is the quickest way to make Markdown and CMS content readable in a Tailwind project. In v4 the setup is one npm install and one @plugin line. Pick a gray scale that matches your UI, add dark:prose-invert, tune links and code with element modifiers, and move to a small @utility theme when the class list gets long.