# Tailwind CSS Typography: prose classes for HTML you do not control

> The official Tailwind plugin that applies typographic defaults to Markdown and CMS output you cannot add classes to. It installs as @tailwindcss/typography, and its main constraint is that it styles descendants rather than the element you mark up.

**tailwindlabs/tailwindcss-typography** — Beautiful typographic defaults for HTML you don't control.

- Repository: https://github.com/tailwindlabs/tailwindcss-typography
- Website: https://tailwindcss-typography.vercel.app/
- Stars: 6,474 · Forks: 316
- Language: JavaScript
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/tailwindlabs-tailwindcss-typography

## The problem: Markdown and CMS HTML arrive without classes

Utility-first CSS assumes you can put classes on the elements you want to style. Rendered Markdown breaks that assumption. A CMS body field, a README pipeline, or a comment thread produces plain h1, p, ul and blockquote tags, and you have no place to attach text-lg or leading-relaxed. Rewriting the renderer to inject classes is the alternative, and it is more work than most teams want.

The plugin takes the other route. You add a single class to a wrapper, and the plugin generates descendant styles for the elements inside it. The README frames the target audience directly: vanilla HTML you do not control, like HTML rendered from Markdown, or pulled from a CMS. That is a narrow, well-defined job, and the plugin does not pretend to be a component library.

## How prose classes work: one wrapper, descendant selectors

The package name is @tailwindcss/typography, and its only runtime dependency is postcss-selector-parser. That detail explains the mechanism. The plugin does not emit a stylesheet of prewritten rules at build time in the naive sense; it uses the selector parser to construct descendant selectors against the elements inside the prose wrapper, then hands those rules to Tailwind so they participate in the normal utility pipeline.

That is why modifiers compose the way they do. prose-slate, prose-xl and prose-invert are not separate stylesheets; they are modifier classes that the plugin's generated rules key off, following what the README calls the multi-class modifier pattern. The README repeats a note twice: always include the prose class when adding a gray scale modifier, and always include it when adding a size modifier. If you write class="prose-slate" alone, the base rules never match and nothing looks different.

Element modifiers extend the same idea to individual tags. prose-a:text-blue-600 styles links inside the wrapper, prose-img:rounded-xl rounds images, and prose-headings:underline covers h1 through h4 plus th. The README lists the full set, from prose-lead for [class~="lead"] down to prose-ol. This is the part of the plugin that earns its keep in practice: it gives you per-element escape hatches without touching the HTML that the renderer produced.

## Installing @tailwindcss/typography and a first article

Install the plugin as a dev dependency. The README uses npm, and the package is published publicly under the @tailwindcss scope.

```bash
npm install -D @tailwindcss/typography
```

For Tailwind CSS v4, the README adds the plugin to your main style.css with a single at-rule rather than a config file. The diff in the README shows one line added after the Tailwind import.

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

If you are on Tailwind CSS v3, the plugin goes in the plugins array of tailwind.config.js instead. The package.json peerDependencies field accepts tailwindcss >=3.0.0 || >=4.0.0 || insiders, so both paths are supported by the same published version.

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

With the plugin registered, wrap your rendered content in an element carrying prose. The README's own example puts the class on an article element and interpolates the Markdown output inside it.

```html
<article class="prose lg:prose-xl">{{ markdown }}</article>
```

What you should see is headings, paragraphs, lists and blockquotes that pick up consistent spacing and sizing without any classes on those elements. From there, two modifiers are worth trying immediately. prose-slate switches the gray scale to match a slate-based UI, and dark:prose-invert flips the palette for dark mode. Combine size and breakpoint modifiers to change scale at viewport widths, as in prose md:prose-lg lg:prose-xl.

## Where the abstraction leaks

The plugin styles descendants of the wrapper, so the wrapper itself is untouched. If you put prose on the same element that also needs a background, border or padding, you are relying on Tailwind utilities for those, which is fine, but it means the class is doing two jobs in one place and the boundary between them is easy to lose track of.

The more practical failure mode is content the plugin has no opinion about. The README's element modifier table enumerates what the plugin targets: headings, paragraphs, links, blockquotes, figures, lists, code, pre, and a handful more. Anything outside that list, such as a custom callout div or a table variant your CMS emits, gets no styling from the plugin at all. You will be writing those rules yourself, and they will sit next to generated ones, which makes debugging specificity more annoying than in a plain utility setup.

There is also a real cost in overriding. Because the plugin's rules target descendants, fighting them means either higher-specificity selectors or the element modifier syntax. The modifier syntax is the intended path, and the README documents it well, but it only exists for the elements in the table. For anything else you are back to ordinary CSS, and ordinary CSS against plugin-generated rules is where most of the frustration in this kind of setup comes from.

## Typography versus Forms: two plugins, two different contracts

The comparison people search for is typography versus forms, and the difference is in what each one assumes about the markup. @tailwindcss/forms resets and normalizes form controls, which are elements the browser styles by default and which you usually do control in your own templates. Typography styles prose elements that a renderer produced.

The practical consequence is that they rarely overlap. A page with a CMS article and a contact form wants both, and the README's install instructions do not conflict with a forms plugin: both are entries in the same plugins array on v3, or separate @plugin lines on v4. If you are choosing between them because of a bundle-size concern, the decision is about which problem you actually have, not about which plugin is better.

The genuine alternative to this plugin is not another plugin but a different architecture: render Markdown to HTML with a component mapping, so that each element becomes a framework component with its own classes. That gives you total control and no descendant-selector layer, at the cost of writing and maintaining the mapping for every element. For a blog with a handful of element types, the mapping is tractable. For arbitrary CMS output, it is not.

## Maintenance, licensing and what an upgrade touches

The repository is not archived, and the last push was on 2026-06-08, the same day as the v0.5.20 release. The version history shows the cadence is not uniform: v0.5.18 and v0.5.19 landed within days of each other in September 2025, then nothing until v0.5.20 in June 2026. Treat this as a plugin that receives fixes and compatibility updates rather than one with a fast-moving feature roadmap.

That matters for upgrade planning because the peerDependencies range covers Tailwind 3, 4 and insiders. When Tailwind itself changes how plugins are registered, this package has to follow, and the README already documents two registration paths for exactly that reason. If you are on v3 and planning a v4 migration, the plugin's config entry moves from the plugins array to an @plugin line in your CSS. That is a small edit, but it is an edit in a file that may also hold your theme configuration.

The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are included. This is a statement about the licence text, not legal advice; check the LICENSE file in the repository and your own organisation's policy if the distinction matters to you. The plugin ships src/*.js, src/*.d.ts and dist/ per the files field, so TypeScript consumers get the bundled declarations without a separate types package.

## Conclusion

Adopt it when you render HTML you cannot annotate with classes, such as Markdown from a CMS or a static site generator, and you want one class to carry the typography. Skip it when you need per-element control over markup you own, or when you want a component library that ships its own heading and paragraph components. Before rolling it out, verify the install path that matches your Tailwind version (the @plugin line for v4, the plugins array for v3), confirm the gray scale you pick matches the rest of the UI, and check whether your content includes tables, since the README's element modifier list is the place to confirm which elements are covered.

## FAQ

### What is Tailwind CSS Typography?

It is the official Tailwind CSS plugin that provides prose classes, which add typographic defaults to vanilla HTML you do not control, such as HTML rendered from Markdown or pulled from a CMS. The README describes it as a set of prose classes for exactly that case.

### How do I install tailwindcss typography?

Install it from npm with npm install -D @tailwindcss/typography. Then add @plugin "@tailwindcss/typography" to your main style.css on Tailwind v4, or add require('@tailwindcss/typography') to the plugins array in tailwind.config.js on v3.

### How do I use tailwindcss typography?

Add the prose class to a wrapper element around your rendered content, for example an article element. Modifiers such as prose-slate, prose-xl and dark:prose-invert adjust the gray scale, size and dark mode, and must be used together with the base prose class.

### What is the difference between tailwindcss typography and forms?

They solve different problems. Typography styles prose elements produced by a renderer, while forms normalizes form controls. The README gives no indication that they conflict, and both register as plugins in the same Tailwind configuration.

### What is an alternative to tailwindcss typography?

Instead of styling descendants of a wrapper, you can map each Markdown element to a framework component that carries its own classes. That removes the descendant-selector layer and gives per-element control, but you have to write and maintain the mapping for every element type.

## Sources

- [License: MIT](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/LICENSE)
- [Project website](https://tailwindcss-typography.vercel.app/)
- [README](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/README.md)
- [Releases](https://github.com/tailwindlabs/tailwindcss-typography/releases)
- [tailwindlabs/tailwindcss-typography on GitHub](https://github.com/tailwindlabs/tailwindcss-typography)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tailwindlabs-tailwindcss-typography
