Type something to search...
Styling Long-Form Content with the Tailwind Typography Plugin

Styling Long-Form Content with the Tailwind Typography Plugin

The first time you render a Markdown post in a Tailwind project, it looks broken. Headings are the same size as paragraphs, lists have no bullets, links aren't underlined, and there's no space between anything. That's Tailwind's base reset, Preflight, doing its job: it removes browser defaults so you can style components from scratch. But you can't add utility classes to HTML that comes out of a Markdown parser or a CMS.

The Tailwind Typography plugin (@tailwindcss/typography) solves this. It adds a prose class that styles every element inside it with carefully tuned typographic defaults: sizes, spacing, line heights, list markers, blockquotes, tables, and code. This guide covers installing it, choosing sizes and color themes, customizing individual elements, handling dark mode, and fitting it to your own design.

Installing the Plugin

Install the package as a dev dependency:

npm install -D @tailwindcss/typography

In Tailwind v4, register it in your main stylesheet with the @plugin directive:

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

In Tailwind v3, add it to the plugins array in tailwind.config.js:

module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx,md,mdx}"],
  plugins: [require("@tailwindcss/typography")],
};

That's all the setup. The plugin doesn't change anything until you use the prose class.

The prose Class

Wrap your rendered content in an element with prose:

export default function PostBody({ html }) {
  return (
    <article
      className="prose mx-auto"
      dangerouslySetInnerHTML={{ __html: html }}
    />
  );
}

Everything inside now has styles: h1 through h4 get a size scale and weights, paragraphs get margins, ul and ol get markers and indentation, blockquote gets a border and italic text, table gets borders and padding, pre and code get a monospace treatment, and images and figures get spacing.

The plugin also sets a max-width of 65ch on the prose element itself. That's a deliberate choice: it keeps line length in the comfortable range for reading. If you're curious why, the ideal line length explains the reasoning behind numbers like this.

If your layout already controls width, remove the cap with max-w-none:

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

Size Modifiers

prose defaults to a base size of 1rem. Size modifiers scale the entire system together, so headings, margins, and line heights stay in proportion:

ClassBody sizeTypical use
prose-sm0.875remSidebars, comments, dense documentation
prose-base1remThe default; resets a larger size at a breakpoint
prose-lg1.125remBlog posts and articles on larger screens
prose-xl1.25remEditorial features, landing page copy
prose-2xl1.5remShort, large-format pieces

They work with responsive variants, which is the most common pattern for articles:

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

Because every element is scaled, you get more than a bigger font. The margin above an h2, the indentation of a list, and the padding in a code block all grow too. That's the main advantage over setting text-lg on a wrapper.

Gray Scale Themes

The plugin includes color themes based on Tailwind's gray palettes. Pick the one that matches the rest of your UI:

<article class="prose prose-slate">...</article>

The options are prose-gray (the default), prose-slate, prose-zinc, prose-neutral, and prose-stone. Each sets body text, headings, links, bold text, counters, bullets, rules, quotes, captions, code, and table borders to shades from that palette.

Dark Mode with prose-invert

For dark backgrounds, add prose-invert. It swaps the colors to a set designed for light text on dark surfaces:

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

With the dark: variant, the inverted colors apply only when dark mode is active. Light text on dark backgrounds often benefits from small changes in weight and spacing as well as color; typography in dark mode covers what to adjust beyond the palette.

Element Modifiers

You'll often want to change one element without rewriting the whole theme. The plugin provides variants that target elements inside the prose container:

<article
  class="prose prose-slate lg:prose-lg
         prose-headings:font-display prose-headings:tracking-tight
         prose-a:text-sky-700 prose-a:underline-offset-2 hover:prose-a:text-sky-900
         prose-blockquote:border-l-sky-500
         prose-img:rounded-xl
         prose-code:before:content-none prose-code:after:content-none"
>
  ...
</article>

The most useful modifiers:

ModifierTargets
prose-headings:h1 to h6 together, plus th
prose-h1: to prose-h4:A single heading level
prose-p:Paragraphs
prose-lead:Elements with the lead class
prose-a:Links
prose-strong:Bold text
prose-ul:, prose-ol:, prose-li:Lists and list items
prose-blockquote:Blockquotes
prose-code:, prose-pre:Inline code and code blocks
prose-img:, prose-figure:, prose-figcaption:Images and figures
prose-table:, prose-th:, prose-td:Tables
prose-hr:Horizontal rules

The last line of the example above deserves explanation. By default, the plugin wraps inline code in visible backtick characters using ::before and ::after. Many sites prefer a background color instead, so removing the backticks with content-none and adding something like prose-code:rounded prose-code:bg-slate-100 prose-code:px-1 is a very common tweak.

Modifiers are great for a handful of changes. If your class list grows past a few lines, move the customization into CSS.

Customizing with CSS Variables

The plugin's colors are driven by CSS custom properties named --tw-prose-*. You can override them in a class of your own, which keeps the markup clean:

.prose-brand {
  --tw-prose-body: #334155;
  --tw-prose-headings: #0f172a;
  --tw-prose-links: #0369a1;
  --tw-prose-bold: #0f172a;
  --tw-prose-quotes: #1e293b;
  --tw-prose-quote-borders: #38bdf8;
  --tw-prose-code: #be185d;
  --tw-prose-bullets: #94a3b8;

  --tw-prose-invert-body: #cbd5e1;
  --tw-prose-invert-headings: #f8fafc;
  --tw-prose-invert-links: #7dd3fc;
  --tw-prose-invert-quote-borders: #0ea5e9;
}
<article class="prose prose-brand dark:prose-invert">...</article>

The invert variables are what prose-invert switches to, so defining both sets gives you a complete light and dark theme.

Customizing Spacing and Sizes

Colors are easy. Sizes and spacing need more care because the plugin computes them as a coordinated set. You can override them with regular CSS that targets the prose children. In Tailwind v4, put these rules in a layer so they sit at the right point in the cascade:

@layer components {
  .prose :where(h2):not(:where([class~="not-prose"] *)) {
    margin-top: 2.5em;
    font-size: var(--text-h2);
    line-height: var(--text-h2--line-height);
  }

  .prose :where(p, li) {
    text-wrap: pretty;
  }

  .prose :where(h1, h2, h3) {
    text-wrap: balance;
  }
}

The plugin wraps its own selectors in :where() to keep specificity low, so simple selectors like these win without !important. Referencing your theme variables, like --text-h2, keeps prose headings in sync with the custom type scale you configured in Tailwind.

In Tailwind v3, the documented approach is the typography key in the config, which accepts plain CSS-in-JS objects per size:

module.exports = {
  theme: {
    extend: {
      typography: ({ theme }) => ({
        DEFAULT: {
          css: {
            "--tw-prose-links": theme("colors.sky.700"),
            a: {
              textUnderlineOffset: "2px",
              fontWeight: "500",
            },
            "code::before": { content: "none" },
            "code::after": { content: "none" },
          },
        },
        lg: {
          css: {
            h2: { marginTop: "2.25em" },
          },
        },
      }),
    },
  },
  plugins: [require("@tailwindcss/typography")],
};

The DEFAULT key applies to every size, while lg and the others apply only with their modifier.

Opting Out with not-prose

Long-form content often includes embedded components: a newsletter signup, a callout, a product card. Those components have their own Tailwind styles and shouldn't inherit prose margins and colors. Wrap them in not-prose:

<article className="prose lg:prose-lg">
  <MDXContent />
  <div className="not-prose my-10 rounded-2xl bg-slate-900 p-8 text-white">
    <h3 className="text-xl font-semibold">Get new posts by email</h3>
    <p className="mt-2 text-slate-300">One email a week. No tracking.</p>
  </div>
</article>

Everything inside not-prose is ignored by the plugin's selectors. One limitation: you can't nest prose back inside a not-prose block. If you need another prose area, close the first one and start a new container.

Using a Custom Class Name

If prose clashes with an existing class, or you want a name that reads better in your codebase, the plugin accepts a className option. In v3:

plugins: [require("@tailwindcss/typography")({ className: "article" })],

Then use article, article-lg, and article-a: modifiers in place of the prose versions. In v4, plugin options go in a block after the @plugin directive; check the plugin's README for the exact syntax supported by your installed version.

When Not to Use It

The Typography plugin is designed for content you don't control at the element level: Markdown, MDX, rich text from a CMS. It's a poor fit for:

  • Application UI. Forms, dashboards, and navigation should use utilities directly. Wrapping them in prose adds margins you then have to fight.
  • Heavily designed editorial layouts. If every article has a custom layout, you'll override so much of the plugin that writing your own base styles is simpler.
  • Very small snippets. A one-sentence description doesn't need a typographic system.

Common Mistakes

  • Forgetting max-w-none and wondering why the article won't fill its column. The 65ch cap is applied to the prose element itself.
  • Fighting specificity with !important. The plugin uses :where(), so a plain selector in a layer usually wins. Reach for !important only after checking the cascade.
  • Leaving the default backticks on inline code when your code style uses a background. Remove them with prose-code:before:content-none prose-code:after:content-none, or in config.
  • Setting text-lg on the wrapper instead of prose-lg. The text grows but the spacing, headings, and list indentation don't.
  • Putting components inside prose without not-prose, then adding resets to every component to undo inherited margins.
  • Only changing light-mode colors. If you override --tw-prose-*, override the matching --tw-prose-invert-* values as well, or dark mode keeps the plugin's defaults.

Conclusion

The Tailwind Typography plugin gives Markdown and CMS content a complete, readable typographic system with one class. Install it, wrap content in prose, pick a size modifier and gray theme, add dark:prose-invert, and use element modifiers or --tw-prose-* variables for the details that make it match your brand.

Start with the defaults and change as little as possible. They're well balanced, and every override is something you have to maintain. When you do customize, prefer variables and low-specificity CSS tied to your theme tokens over long chains of modifiers, and keep embedded components out of the cascade with not-prose.

Share :

Related Posts

A Short History of Typography: From Gutenberg to Variable Fonts

A Short History of Typography: From Gutenberg to Variable Fonts

When you write font-weight: 600 or font-style: italic, you're using vocabulary that is hundreds of years old. "Italic" comes from Renaissance Ita

Continue Reading
Accessible Typography: Font Size, Contrast, and Spacing Guidelines

Accessible Typography: Font Size, Contrast, and Spacing Guidelines

Most accessibility work on a website involves text. Navigation labels, form instructions, error messages, article bodies, button captions: if people

Continue Reading
Bauhaus Typography and Its Influence on Modern Design

Bauhaus Typography and Its Influence on Modern Design

Bold sans-serif type, thick black bars, red accents, text running at an angle, and lowercase-only headlines. If a poster or website uses that combina

Continue Reading