Tailwind CSS Typography easy beautiful design

The @tailwindcss/typography plugin is a practical, yet somewhat unconventional, "counter-example" in the Tailwind CSS ecosystem. While Tailwind typically emphasizes utility-first design, this plugin introduces a compositional abstraction (the prose class). It tackles styling challenges for semantic HTML content that's rich in text but simple in structure—a common scenario in real-world projects.This plugin isn't a one-size-fits-all solution for every UI. However, for content-driven pages like blogs, knowledge bases, or educational platforms, it's an incredibly cost-effective tool for achieving beautiful typography.

tailwindcsstailwindcss typographytailwindcss prosetailwindcss pluginrich text stylingresponsive typographytailwindcss customizationnextjs typography integration

Before & After

Here are the before and after effects of using Tailwind CSS Typography.

  • The left side shows the original HTML without any typographic optimization, using the browser's default User Agent Styles.
  • The right side shows the result after applying the default prose class from Typography.
❌ Without Typography

H1: Technical Document Title

H2: Section Title

H3: Sub-section Title

This is a paragraph to demonstrate the browser's default stylesheet, or User Agent Stylesheet.

You can write some bold, italic, or code text.

This is a blockquote.
  • Unordered list item A
  • Unordered list item B
  1. Ordered list 1
  2. Ordered list 2
function greet(name) {
  return 'Hello, ' + name + '!';
}
ItemDescription
HTMLHypertext Markup Language
CSSCascading Style Sheets
✅ With Typography

H1: Technical Document Title

H2: Section Title

H3: Sub-section Title

This is a paragraph to demonstrate Typography styles. It shows the visual effects of line height, letter spacing, paragraph spacing, and more.

You can write some bold, italic, or code text.

This is a blockquote.
  • Unordered list item A
  • Unordered list item B
  1. Ordered list 1
  2. Ordered list 2
function greet(name) {
  return 'Hello, ' + name + '!';
}
ItemDescription
HTMLHypertext Markup Language
CSSCascading Style Sheets

💡Note: Only one class, class="prose", was added.

The comparison clearly shows that the Tailwind CSS Typography plugin's prose class provides a significant boost in both visual appeal and readability, including:

  • Improved font size and line height for greater reading comfort
  • Appropriate spacing between paragraphs and headings for clearer content hierarchy
  • More aesthetically pleasing list styles (bullet points, indentation, line spacing)
  • Unified and optimized presentation of code snippets (font, background, margins)
  • Professional typographic treatment for elements like blockquotes and tables

Overall, prose helps raw HTML content achieve a more readable and designed layout, making it ideal for content-heavy pages like technical documentation and blog posts.

Introduction to the Typography Plugin

If you're interested in the layout we just looked at, we'll now dive into the origins, purpose, and use cases for this plugin.

  • What is Typography?
  • What is its purpose?
  • What are its typical use cases?
  • Who is it for?

In recent years, Tailwind CSS has become the "default choice" for many modern front-end projects, widely used in blogs, documentation, and admin dashboards. Even the official websites for AI products like OpenAI use it. This trend alone makes it worthwhile to understand the design philosophy and practical value behind it.

Tailwind CSS is a "utility-first" CSS framework. It provides a vast number of atomic (single-purpose) class names that let you build complex user interfaces directly in your HTML, without writing traditional CSS. Unlike "component-based" frameworks such as Bootstrap or Foundation, Tailwind doesn't offer pre-built components like buttons, cards, or navigation bars. Instead, it provides the low-level building blocks needed to create these components (e.g., text-center, bg-blue-500, p-4, etc.).

What is Typography?

The Tailwind CSS Typography plugin (also known as @tailwindcss/typography or the Prose plugin) is an official plugin released by Tailwind Labs around 2020. It introduced the concept of prose, which consolidates a set of rich text styles into a reusable class name. Its purpose is to provide developers with "elegant, sensible, and semantic rich text styles" specifically for long-form HTML content, such as articles, blog posts, and Markdown-rendered output.

By using the prose class name, it provides a default set of typographic styles to beautify and standardize the appearance of common HTML elements within an article, including paragraphs, headings, lists, code blocks, blockquotes, images, and tables.

What is its purpose?

While you can add classes to any HTML element to control its style in a native Tailwind project or by writing custom CSS, it becomes quite tedious when styling rich text content with a complex, deep hierarchy (e.g., articles rendered from a CMS or Markdown).

The core uses of the Typography plugin are to:

  • Provide consistent, beautiful default styling for HTML that has not been decorated with custom class names.
  • Simplify the styling of content outputted from Markdown or a CMS.
    • This is especially useful for scenarios where Markdown is rendered to HTML (e.g., MDX, remark, rehype), as the plugin can directly handle the styling of semantic content without requiring manual class annotation.
  • Offer an "out-of-the-box" elegant typographic experience.
  • Support theme customization and dark mode through configurable themes.

When to Use It

✅ Perfect for

  • Blogs
  • Technical documentation and knowledge bases
  • Pages rendering Markdown content (e.g., .mdx files in Next.js)
  • News and article display pages
  • Educational content platforms

While powerful, the Typography plugin isn't the right fit for every type of page:

🚫 Not a Great Fit

  • Highly componentized UI modules that require significant visual freedom
  • Custom structures and non-standard semantic content (e.g., complex forms, interactive charts)

Who It's For

This plugin is ideal for a few different types of users:

Beginners

  • Get beautiful, well-formatted pages quickly without needing to manually tweak the style of every single HTML tag.

Intermediate to Advanced Developers

  • Customize themes or extend the plugin to create typography that matches a specific design system.
  • Significantly reduce workload when building content platforms, blogs, or static sites.

Content Creators/Publishers

  • Use it in conjunction with architectures like Headless CMS or Notion-to-blog to automatically style generated articles.

Now, let's get hands-on and see how to use it in a project, along with its basic configuration and best practices.

Step-by-Step Usage Guide

For the following demonstration, we'll use Next.js as the host framework with Tailwind CSS v4. Even if you're not familiar with Next.js, you can follow along step-by-step and run it locally. Our main focus will be on the practical effects of the Typography plugin.

Prerequisites:

  • Node.js version 18.18 or higher installed on your machine.
  • pnpm installed.

Create the Application

  1. First, create a Next.js application using pnpm as the package manager, which is the official recommended method for Next.js. The process is as follows:

    Navigate to your project directory in the terminal and enter the following command:

npx create-next-app@latest --use-pnpm
 
# If not installed locally, it will automatically download and install the latest version from the npm registry.
# When prompted, simply type `y` to confirm.
Need to install the following packages:
create-next-app@15.3.5
Ok to proceed? (y)

create-next-app will prompt you for the project name and to confirm options. Enter tailwindcss-typography as the project name, and keep all other options as default.

Make sure the code is not placed in the src directory to avoid path inconsistencies later. tailwindcss will be integrated by default.

 What is your project named? tailwindcss-typography
 Would you like to use TypeScript? No / Yes
 Would you like to use ESLint? No / Yes
 Would you like to use Tailwind CSS? No / Yes
 Would you like your code inside a `src/` directory? No / Yes
 Would you like to use App Router? (recommended) … No / Yes
 Would you like to use Turbopack for `next dev`? … No / Yes
 Would you like to customize the import alias (`@/*` by default)? … No / Yes

Enter the following command in the terminal to enter the directory of the project just created, here we use tailwindcss-typography

cd tailwindcss-typography
  1. Install @tailwindcss/typography, we input the following command to install the typography plugin
pnpm add -D @tailwindcss/typography

Use your familiar editor, such as visual studio code, cursor, windsurf, trae, etc., to open this directory, view the package.json file, and in the devDependencies section, you should see the plugin information, as follows:

{
  ...
  
  "devDependencies": {
   ...
    "@tailwindcss/typography": "^0.5.16",
    "tailwindcss": "^4",
    ...
  }
}

That concludes the project initialization.

In the next steps, don't worry if you're unfamiliar with concepts like Next.js, React, or TypeScript. We'll be focusing specifically on the Typography plugin. The core ideas and usage of Typography remain the same, whether you're using JavaScript, Vite, or Vue, so you can still follow along easily.

Understand prose

Open the editor, complete the following two operations:

  • Edit the globals.css file in the app directory, add the plugin code after the second line, i.e., @import "tailwindcss";, as follows:
@import "tailwindcss";
@plugin "@tailwindcss/typography";
  • Edit the page.tsx file in the app directory, delete all the content inside, and replace it with the following code:
import Link from "next/link"
 
export default function Home() {
	return (
		<div className="flex flex-row space-x-6 items-start justify-center h-screen w-screen p-8">
			<div>
				<h1>❌ Without Typography</h1>
				<h2>H2: Section Heading</h2>
				<h3>H3: Subsection Heading</h3>
				<p>This section doesn't use Typography styles and relies on the browser's default stylesheet, or User Agent Stylesheet.</p>
				<p>
					You can write text that is <span style={{ fontWeight: "bold" }}>bold</span>, <em>italicized</em>, or contains <code>code snippets</code>.
				</p>
				<blockquote>This is a blockquote area.</blockquote>
				<ul>
					<li>Unordered List Item A</li>
					<li>Unordered List Item B</li>
				</ul>
				<ol>
					<li>Ordered List 1</li>
					<li>Ordered List 2</li>
				</ol>
				<pre>
					<code>{`function greet(name) {
  return 'Hello, ' + name + '!';
}`}</code>
				</pre>
				<table>
					<thead>
						<tr>
							<th>Item</th>
							<th>Description</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>HTML</td>
							<td>Hypertext Markup Language</td>
						</tr>
						<tr>
							<td>CSS</td>
							<td>Cascading Style Sheets</td>
						</tr>
					</tbody>
				</table>
 
				<h4>Feature Showcase</h4>
				<ul>
					<li>
						<Link href="/1-size-modifiers">Size Modifiers (Built-in Font Size Adjustment Tools)</Link>
					</li>
					<li>
						<Link href="/2-element-modifiers">Element Modifiers (Customization)</Link>
					</li>
					<li>
						<Link href="/3-tailwind-config-js">Tailwind Config JS Customization</Link>
					</li>
					<li>
						<Link href="/4-gray-scale">Gray Scale Theme</Link>
					</li>
					<li>
						<Link href="/5-override-max-width">Override Default Max-Width</Link>
					</li>
					<li>
						<Link href="/6-undo-style">Undo Prose Styles</Link>
					</li>
				</ul>
			</div>
 
			<div className="prose dark:prose-invert">
				<h1>✅ With Typography</h1>
				<h2>H2: Section Heading</h2>
				<h3>H3: Subsection Heading</h3>
				<p>This is a paragraph to demonstrate Typography styles. It shows the visual effects of line height, letter spacing, paragraph spacing, and more.</p>
				<p>
					You can write text that is <span style={{ fontWeight: "bold" }}>bold</span>, <em>italicized</em>, or contains <code>code snippets</code>.
				</p>
				<blockquote>This is a blockquote area.</blockquote>
				<ul>
					<li>Unordered List Item A</li>
					<li>Unordered List Item B</li>
				</ul>
				<ol>
					<li>Ordered List 1</li>
					<li>Ordered List 2</li>
				</ol>
				<pre>
					<code>{`function greet(name) {
  return 'Hello, ' + name + '!';
}`}</code>
				</pre>
				<table>
					<thead>
						<tr>
							<th>Item</th>
							<th>Description</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>HTML</td>
							<td>Hypertext Markup Language</td>
						</tr>
						<tr>
							<td>CSS</td>
							<td>Cascading Style Sheets</td>
						</tr>
					</tbody>
				</table>
 
				<h4>Feature Showcase</h4>
				<ul>
					<li>
						<Link href="/1-size-modifiers">Size Modifiers (Built-in Font Size Adjustment Tools)</Link>
					</li>
					<li>
						<Link href="/2-element-modifiers">Element Modifiers (Customization)</Link>
					</li>
					<li>
						<Link href="/3-tailwind-config-js">Tailwind Config JS Customization</Link>
					</li>
					<li>
						<Link href="/4-gray-scale">Gray Scale Theme</Link>
					</li>
					<li>
						<Link href="/5-override-max-width">Override Default Max-Width</Link>
					</li>
					<li>
						<Link href="/6-undo-style">Undo Prose Styles</Link>
					</li>
				</ul>
			</div>
		</div>
	)
}

Go back to the terminal, enter the application directory, and input the startup command to start the development server. At the same time, enter the address http://localhost:3000 in the browser, the default port is 3000.

pnpm dev
 
> tailwindcss-typography@0.1.0 dev /tailwindcss-typography
> next dev --turbopack
 
 Next.js 15.3.5 (Turbopack)
   - Local:        http://localhost:3000
   - Network:      http://192.168.5.35:3000

When you open the browser, you'll see the default browser styles (User Agent Styles) on the left and the typography styles on the right, consistent with the initial comparison.

If you look at the code, you'll notice that, apart from the Tailwind CSS utility classes like flex flex-row space-x-6..., the only difference between the two div elements is the addition of a single class: class="prose".

<div>
	...
</div>
 
<div class="prose">
	...
</div>

Next, let's explore what's happening behind the scenes and what Tailwind CSS and Typography are doing.

When you add the prose class to an HTML element, the combination of Tailwind CSS and the @tailwindcss/typography plugin does a lot of work. Unlike other atomic Tailwind classes that apply just one or two styles, prose injects an entire set of typographic styles optimized for long-form content.

prose is a predefined "typographic style set for article content." It works by setting default styles for nested elements like h1 through h6, p, ul, ol, blockquote, code, and more, giving raw HTML a more elegant, structured, and readable layout.

From a technical perspective, once you've installed the Typography plugin and applied the prose class, Tailwind scans your HTML, MDX, TSX, and other files. When it finds the prose class, it compiles this entire set of typographic styles into your final CSS file.

Unlike a Tailwind utility class like text-lg or text-blue-100 that applies to a single element, prose is a "style set that acts on child elements." It generates code that looks like this:

.prose h1 { font-size: 2.25rem; font-weight: 800; line-height: 1.2; }
.prose p { margin-top: 1.25em; margin-bottom: 1.25em; }
.prose ul { padding-left: 1.5em; list-style-type: disc; }
.prose code { background-color: rgba(0,0,0,0.05); padding: 0.2em 0.4em; }
.prose blockquote { border-left: 0.25em solid #ccc; padding-left: 1em; color: #666; font-style: italic; }
/* ... hundreds of styles */

In short: By simply adding the .prose class to an element, the Tailwind Typography plugin will automatically style common tags within your article content—like paragraphs, headings, lists, blockquotes, code blocks, and tables. This makes them look clean and professional, just like a blog or technical document, without you having to write any extra CSS.

When using responsive design, you should add the following to your <head> section:

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

Why do you need to add <meta name=....?

  • The purpose of the meta tag is to tell the browser how to control the viewport:
  • width=device-width: This sets the viewport's width to the device's own width. For example, on a mobile phone, it makes the page's width match the physical screen width. Without this, the browser might default to rendering the page at a larger size (e.g., 980px) and then shrink it to fit the screen, leading to tiny text and a jumbled layout.
  • initial-scale=1.0: This sets the initial zoom level of the page to 1.0, meaning no zoom is applied on first load. Combined with width=device-width, it ensures a 1:1 relationship between CSS pixels and device-independent pixels, allowing responsive design to work as intended.
  • Without this meta tag, the browser cannot correctly identify the device's screen width. As a result, Tailwind CSS responsive classes (e.g., md:, lg:, etc.) will not work as expected on different screen sizes because the browser won't trigger the correct breakpoints.

Typography also provides five out-of-the-box responsive size modifiers and grayscale modifiers. Let's continue to explore them.

Responsive Size Modifiers

First, it's important to understand that Tailwind CSS uses a mobile-first strategy. This means the default styles are applied to the smallest viewport (mobile), and then you use built-in breakpoint prefixes to override styles for larger screens. This strategy allows you to implement responsive design.

Tailwind CSS comes with five common breakpoints:

Breakpoint prefixMinimum widthCSS
sm40rem (640px)@media (width >= 40rem) { ... }
md48rem (768px)@media (width >= 48rem) { ... }
lg64rem (1024px)@media (width >= 64rem) { ... }
xl80rem (1280px)@media (width >= 80rem) { ... }
2xl96rem (1536px)@media (width >= 96rem) { ... }

The understanding of breakpoints: When the screen width reaches a certain width (1280px), the corresponding typographic style is applied, which is essentially a media query in CSS, the most basic form of responsive design.

Similarly, Typography also provides five different font sizes, size modifiers.

ClassBody font size
prose-sm0.875rem (14px)
prose-base (default)1rem (16px)
prose-lg1.125rem (18px)
prose-xl1.25rem (20px)
prose-2xl1.5rem (24px)

They can be used with breakpoint prefixes or individually.

For example, prose lg:prose-xl means that the default uses prose (mobile, smaller font), and when the page width ≥ 1024px, the font is increased overall by size modifiers, i.e., prose-xl.

⚠️, when adding size modifiers, you must always include the prose class.

The size adjustment parameters provided by typography are carefully tuned by professional designers to achieve the most perfect visual effect. The parameters cover aspects such as the relationship between font sizes, title spacing, code block margins, and more. Compared to non-design professionals, they are much more aesthetically pleasing, and even if not used, these design patterns are also valuable references.

You can view the default styles of the plugin styles.js to see examples of each modifier in detail.

Let's look at the effect of different sizes.

In the app directory, create a 1-size-modifiers directory, create a page.tsx file in that directory, and copy the following code:

export const metadata = {
	title: "Size Modifiers",
	description: "Size Modifiers",
}
 
export default function SizeModifiersPage() {
	const generateHtmlCode = (title: string = "Technical Documentation Title", subtitle: string = "Section Heading") => {
		return `
            <h1>H1: ${title}</h1>
            <h2>H2: ${subtitle}</h2>
            <h3>H3: Subsection Heading</h3>
            <p>Tailwind CSS follows a mobile-first design principle. This means that, by default, CSS rules without any modifiers (like text-blue-500 or p-4) are applied to all screen sizes, including the smallest mobile device screens. When you use breakpoint prefixes such as sm:, md:, or lg:, you are adding or overriding styles for screens that are larger than (or equal to) that size. Breakpoints are cumulative and overriding. If you only use sm: and not md: or lg:, the sm: styles will be active from 640px and will continue to apply indefinitely (or until a different breakpoint style overrides them).</p>
            <p>
                You can write text that is <span style="font-weight: bold;">bold</span>, <em>italicized</em>, or contains <code>code snippets</code>.
            </p>
            <blockquote>This is a blockquote area.</blockquote>
            <ul>
                <li>Unordered List Item A</li>
                <li>Unordered List Item B</li>
            </ul>
            <ol>
                <li>Ordered List 1</li>
                <li>Ordered List 2</li>
            </ol>
            <pre><code>function greet(name) {
  return 'Hello, ' + name + '!';
}</code>
            </pre>
            <table>
                <thead>
                    <tr>
                        <th>Item</th>
                        <th>Description</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>HTML</td>
                        <td>Hypertext Markup Language</td>
                    </tr>
                    <tr>
                        <td>CSS</td>
                        <td>Cascading Style Sheets</td>
                    </tr>
                </tbody>
            </table>`
	}
 
	const configurations = [
		{ proseClass: "prose-sm", title: "prose-sm 0.875rem (14px)", subtitle: "For screens 640px and up" },
		{ proseClass: "prose-base", title: "prose-base 1rem (16px)", subtitle: "Can also be written as 'prose', this is the default and applies to screens 768px and up" },
		{ proseClass: "prose-lg", title: "prose-lg 1.125rem (18px)", subtitle: "For screens 1024px and up" },
		{ proseClass: "prose-xl", title: "prose-xl 1.25rem (20px)", subtitle: "For screens 1280px and up" },
		{ proseClass: "prose-2xl", title: "prose-2xl 1.5rem (24px)", subtitle: "For screens 1536px and up" },
	]
 
	return (
		<div className="flex flex-row space-x-4 p-3">
			{configurations.map((config, index) => (
				<div
					key={index}
					className={`w-1/5 border-1 border-gray-300 p-2 prose dark:prose-invert ${config.proseClass}`}
					dangerouslySetInnerHTML={{
						__html: generateHtmlCode(config.title, config.subtitle),
					}}
				/>
			))}
		</div>
	)
}

Then go back to the browser, in the application homepage: feature display --> click: - Size Modifiers built-in font size adjustment tool, or enter the address directly: http://localhost:3000/1-size-modifiers, you can see the actual effect of 5 different font sizes. Later, you can combine the tailwindcss breakpoint prefix and font modifier to adjust the font size at different resolutions.

Customizing Styles

If the color or font size of the default modifier needs to be adjusted, we have the following几种方式:

1. Element modifiers

Use element modifiers directly in HTML to customize the styles of each element in the content.

In the app directory, create a 2-element-modifiers directory, create a page.tsx file in that directory, copy the following code, then visit the address http://localhost:3000/2-element-modifiers in the browser to see the effect of the custom styles.

import Link from "next/link"
 
export default function Home() {
	return (
		<div className="flex flex-row space-x-6 items-start justify-center h-screen w-screen p-8">
			<div className="prose dark:prose-invert">
				<h1>⚙️ Default Styles</h1>
				<h2>H2: Section Heading</h2>
				<h3>H3: Subsection Heading</h3>
				<p>This content does not use Typography styles, and instead uses the browser's default styles, also known as the User Agent Stylesheet.</p>
				<p>
					You can write text that is <span style={{ fontWeight: "bold" }}>bold</span>, <em>italicized</em>, or contains <code>code snippets</code>.
				</p>
				<blockquote>This is a blockquote area.</blockquote>
				<ul>
					<li>Unordered List Item A</li>
					<li>Unordered List Item B</li>
				</ul>
				<ol>
					<li>Ordered List 1</li>
					<li>Ordered List 2</li>
				</ol>
				<pre>
					<code>{`function greet(name) {
  return 'Hello, ' + name + '!';
}`}</code>
				</pre>
				<table>
					<thead>
						<tr>
							<th>Item</th>
							<th>Description</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>HTML</td>
							<td>Hypertext Markup Language</td>
						</tr>
						<tr>
							<td>CSS</td>
							<td>Cascading Style Sheets</td>
						</tr>
					</tbody>
				</table>
 
				<h4>Feature Showcase</h4>
				<ul>
					<li>
						<Link href="/size-modifiers">Size Modifiers (Built-in Font Size Adjustment Tools)</Link>
					</li>
					<li>
						<Link href="/size-modifiers">Custom Font Size & Color</Link>
					</li>
					<li>
						<Link href="/size-modifiers">Without Typography Styles</Link>
					</li>
					<li>
						<Link href="/gray-scale">Grayscale Theme</Link>
					</li>
				</ul>
			</div>
 
			<div className="prose dark:prose-invert prose-h1:text-blue-600 prose-h2:text-base prose-h3:text-pink-400 prose-h3:border prose-h3:border-orange-600 prose-h3:rounded-md prose-h3:p-2 prose-p:italic prose-table:border-collapse prose-table:border prose-table:border-gray-300 prose-th:border prose-th:border-gray-300 prose-th:text-center prose-td:border prose-td:border-gray-300 prose-td:text-center">
				<h1>✍️ Using Element Modifiers for Customization. prose-h1:text-blue-600</h1>
				<h2>H2: Section Heading, using prose-h2:text-base to set the h2 font size to base</h2>
				<h3>H3: Subsection Heading, customized with prose-h3:text-pink-400 prose-h3:border prose-h3:border-orange-600 prose-h3:rounded-md prose-h3:p-2</h3>
				<p>This is a paragraph to demonstrate typography styles, showing the visual effects of line height, letter spacing, paragraph spacing, and more. prose-p:italic</p>
				<p>
					You can write text that is <span style={{ fontWeight: "bold" }}>bold</span>, <em>italicized</em>, or contains <code>code snippets</code>.
				</p>
				<blockquote>This is a blockquote area.</blockquote>
				<ul>
					<li>Unordered List Item A</li>
					<li>Unordered List Item B</li>
				</ul>
				<ol>
					<li>Ordered List 1</li>
					<li>Ordered List 2</li>
				</ol>
				<pre>
					<code>{`function greet(name) {
  return 'Hello, ' + name + '!';
}`}</code>
				</pre>
				<table>
					<thead>
						<tr>
							<th>Item</th>
							<th>Description</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>HTML</td>
							<td>Hypertext Markup Language</td>
						</tr>
						<tr>
							<td>CSS</td>
							<td>Cascading Style Sheets</td>
						</tr>
						<tr>
							<td>Example Styles</td>
							<td>
								prose-table:border-collapse prose-table:border prose-table:border-gray-300 prose-th:border prose-th:border-gray-300 prose-th:text-center prose-td:border
								prose-td:border-gray-300 prose-td:text-center
							</td>
						</tr>
					</tbody>
				</table>
 
				<h4>Feature Showcase</h4>
				<ul>
					<li>
						<Link href="/1-size-modifiers">Size Modifiers (Built-in Font Size Adjustment Tools)</Link>
					</li>
					<li>
						<Link href="/2-element-modifiers">Element Modifiers (Customization)</Link>
					</li>
					<li>
						<Link href="/3-tailwind-config-js">Tailwind Config JS Customization</Link>
					</li>
					<li>
						<Link href="/4-gray-scale">Grayscale Theme</Link>
					</li>
					<li>
						<Link href="/5-override-max-width">Override Default Max-Width</Link>
					</li>
					<li>
						<Link href="/6-undo-style">Undo Prose Styles</Link>
					</li>
				</ul>
			</div>
		</div>
	)
}

If everything went smoothly, the styles for your h1, h2, h3, p, and table elements will have changed. This is the simplest approach. You can quickly adjust the styles you need using the prose-[element]:[tailwindcss utility] syntax.

You can view the tailwindcss utitlity related documentation to see the specific effects;

The following table is all modifiers:

ModifierTarget
prose-headings:{utility}h1h2h3h4th
prose-lead:{utility}[class~="lead"]
prose-h1:{utility}h1
prose-h2:{utility}h2
prose-h3:{utility}h3
prose-h4:{utility}h4
prose-p:{utility}p
prose-a:{utility}a
prose-blockquote:{utility}blockquote
prose-figure:{utility}figure
prose-figcaption:{utility}figcaption
prose-strong:{utility}strong
prose-em:{utility}em
prose-kbd:{utility}kbd
prose-code:{utility}code
prose-pre:{utility}pre
prose-ol:{utility}ol
prose-ul:{utility}ul
prose-li:{utility}li
prose-table:{utility}table
prose-thead:{utility}thead
prose-tr:{utility}tr
prose-th:{utility}th
prose-td:{utility}td
prose-img:{utility}img
prose-video:{utility}video
prose-hr:{utility}hr
2. Global Modification

While Element Modifiers are convenient, they're best suited for projects with a limited number of pages. If your application has many pages that all need the same adjustments, the workload can become significant. In this scenario, a "global modification" approach is necessary, which is accomplished by configuring your tailwind.config.js file.

Let's achieve the same effect as before, but this time by following a step-by-step process. We'll explain the concepts afterward.

First, create a tailwind.config.js file in your root directory and copy the following code into it:

/** @type {import('tailwindcss').Config} */
module.exports = {
	theme: {
		extend: {
			typography: () => ({
				DEFAULT: {
					css: {
						h1: {
							color: 'var(--color-blue-600)'
						},
						h2: {
							fontSize: 'var(--font-size-base)'
						},
						h3: {
							color: 'var(--color-pink-400)',
							border: '1px solid var(--color-orange-600)',
							padding: '8px',
							borderRadius: '0.6rem'
						},
						p: {
							fontStyle: 'italic'
						},
						table: {
							borderCollapse: 'collapse',
							border: '1px solid var(--color-gray-300)'
						},
						th: {
							border: '1px solid var(--color-gray-300)',
							textAlign: 'center'
						},
						td: {
							border: '1px solid var(--color-gray-300)',
							textAlign: 'center'
						}
					},
				},
				xl: {
					css: {
						h1: {
							color: 'blue'
						}
					}
				},
				extend: {
					css: {
						h1: {
							color: 'green'
						}
					}
				}
			}),
		},
	},
}

In the app directory, create a custom.css file, and enter the following code:

@import "tailwindcss";
@plugin "@tailwindcss/typography";
@config "../tailwind.config.js";
  • custom.css is in the app directory, and tailwind.config.js is in the project root directory, so the path uses ../, meaning the parent directory of this directory.
  • @config is a Tailwind CSS directive used to load JavaScript-based configurations.

In the app directory, create a 3-tailwind-config-js directory, create a page.tsx file in that directory, and copy the following code:

import Link from "next/link"
import "../custom.css"
 
export default function TailwindConfigJS() {
	return (
		<div className="flex flex-row space-x-6 items-start justify-center h-screen w-screen p-8">
			<div className="prose dark:prose-invert">
				<h1>⚙️ Tailwind Config JS Styles</h1>
				<h2>H2: Section Heading</h2>
				<h3>H3: Subsection Heading</h3>
				<p>This content does not use Typography styles, and instead uses the browser's default styles, also known as the User Agent Stylesheet.</p>
				<p>
					You can write text that is <span style={{ fontWeight: "bold" }}>bold</span>, <em>italicized</em>, or contains <code>code snippets</code>.
				</p>
				<blockquote>This is a blockquote area.</blockquote>
				<ul>
					<li>Unordered List Item A</li>
					<li>Unordered List Item B</li>
				</ul>
				<ol>
					<li>Ordered List 1</li>
					<li>Ordered List 2</li>
				</ol>
				<pre>
					<code>{`function greet(name) {
  return 'Hello, ' + name + '!';
}`}</code>
				</pre>
				<table>
					<thead>
						<tr>
							<th>Item</th>
							<th>Description</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>HTML</td>
							<td>Hypertext Markup Language</td>
						</tr>
						<tr>
							<td>CSS</td>
							<td>Cascading Style Sheets</td>
						</tr>
					</tbody>
				</table>
 
				<h4>Feature Showcase</h4>
				<ul>
					<li>
						<Link href="/1-size-modifiers">Size Modifiers (Built-in Font Size Adjustment Tools)</Link>
					</li>
					<li>
						<Link href="/2-element-modifiers">Element Modifiers (Customization)</Link>
					</li>
					<li>
						<Link href="/3-tailwind-config-js">Tailwind Config JS Customization</Link>
					</li>
					<li>
						<Link href="/4-gray-scale">Grayscale Theme</Link>
					</li>
					<li>
						<Link href="/5-override-max-width">Override Default Max-Width</Link>
					</li>
					<li>
						<Link href="/6-undo-style">Undo Prose Styles</Link>
					</li>
				</ul>
			</div>
		</div>
	)
}

Note that the import "../custom.css" at the top of the file.

Then, in the browser, enter http://localhost:3000/3-tailwind-config-js, and the same changes will occur.

Let's explain the related code.

tailwind.config.js is a JavaScript file that can define or extend the default behavior of Tailwind in this file. In the Tailwind CSS v3 version, this file is essential. V4 removed the dependency on this file, but if you use the typography plugin for custom extensions, this file is essential.

Using JavaScript means you can implement: dynamic calculation, modularity, conditional logic, and extensibility. During the build process, Tailwind CSS reads this file and generates the final CSS based on its configuration, so we'll focus on the configuration content.

DEFAULT uses CSS-in-JS syntax to represent CSS rules.

  • Traditional CSS consists of strings composed of selectors, properties, and values. CSS-in-JS syntax, however, typically uses JavaScript objects to represent these CSS rules.

  • Property names are usually camelCase: For example, background-color in CSS becomes backgroundColor in a JavaScript object.

  • Values are typically strings: Most CSS values are still strings, such as '#fff' or '1rem'.

  • Representing nested rules: For nested CSS rules (like media queries, pseudo-classes, and pseudo-elements), you typically use nested objects or a specific syntax within the object.

  • Use DEFAULT to override the default style rules. It uses some variables from Tailwind CSS; if you're not familiar with them, you can directly use traditional CSS values instead.

  • Similarly, xl overrides the color of h1 in the built-in prose-xl theme.

  • If you don't want to modify the built-in colors, you can use a custom name, such as extend. When you use it, you just need to pass prose:extend as the value; the name is customizable.

custom.css is where you define your custom configurations to be loaded. By importing this stylesheet on the pages that need it, you can handle global styles.

⚠️ Although tailwindcss and typography are already imported in globals.css, adding custom configurations to that file would affect global styles. To demonstrate the functionality, we'll add this separate file and re-import tailwindcss and typography. In a production environment, you should avoid this practice. The Next.js + Tailwind build process works like this: as long as a CSS file is imported, it serves as a build entry point. This means that every imported CSS file goes through a separate Tailwind compilation process. As a result, globals.css and custom.css become two independent Tailwind build entry points, which can use different configurations (and even plugins or presets). However, this also loses the core benefits of Tailwind's unified, on-demand generation, and can lead to a larger build size and duplicate styles.

Using this method, you can define different themes, such as blue, red, and so on.

Grayscale Themes & Style Notes

Similarly, this plugin provides 5 different grayscale themes by default.

In the app directory, create a 4-gray-scale directory, create a page.tsx file in that directory, and copy the following code:

export const metadata = {
	title: "Tailwind Typography Grayscale Themes Demo",
	description: "A visual comparison of prose-gray, slate, zinc, neutral, and stone themes and their design intent.",
}
 
export default function GrayScalePage() {
	const generateHtmlCode = (proseClass: string, title: string = "Technical Documentation Title", subtitle: string = "Section Heading") => {
		return `<div class="${proseClass} dark:prose-invert">
            <h1>H1: ${title}</h1>
            <h2>H2: ${subtitle}</h2>
            <h3>H3: Subsection Heading</h3>
            <p>This is a paragraph to demonstrate the styles of the \`p\` tag, including line height, letter spacing, and paragraph spacing.</p>
            <p>
                You can write text that is <strong>bold</strong>, <em>italicized</em>, or contains <code>code snippets</code>.
            </p>
            <blockquote>This is a blockquote area.</blockquote>
            <ul>
                <li>Unordered List Item A</li>
                <li>Unordered List Item B</li>
            </ul>
            <ol>
                <li>Ordered List 1</li>
                <li>Ordered List 2</li>
            </ol>
            <pre><code>function greet(name) {
  return 'Hello, ' + name + '!';
}</code></pre>
            <table>
                <thead>
                    <tr><th>Item</th><th>Description</th></tr>
                </thead>
                <tbody>
                    <tr><td>HTML</td><td>Hypertext Markup Language</td></tr>
                    <tr><td>CSS</td><td>Cascading Style Sheets</td></tr>
                </tbody>
            </table>
        </div>`
	}
 
	const configurations = [
		{ proseClass: "prose-gray", title: "Grayscale Theme: gray", subtitle: "Gray Section" },
		{ proseClass: "prose-slate", title: "Slate Theme: slate", subtitle: "Slate Section" },
		{ proseClass: "prose-zinc", title: "Zinc Theme: zinc", subtitle: "Zinc Section" },
		{ proseClass: "prose-neutral", title: "Neutral Theme: neutral", subtitle: "Neutral Section" },
		{ proseClass: "prose-stone", title: "Stone Theme: stone", subtitle: "Stone Section" },
	]
 
	return (
		<div className="p-4 w-screen space-y-4">
			{/* Top explanatory text */}
			<section className="max-w-5xl prose dark:prose-invert">
				<h1>Tailwind Typography Grayscale Themes Comparison</h1>
				<p>
					Tailwind offers <code>5</code> grayscale typography themes designed to align with the gray tones of different design languages.
				</p>
				<table>
					<thead>
						<tr>
							<th>Name</th>
							<th>Style</th>
							<th>Color Temperature</th>
							<th>Feel</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>gray</td>
							<td>Default / General</td>
							<td>Neutral-warm</td>
							<td>Most universal and traditional</td>
						</tr>
						<tr>
							<td>slate</td>
							<td>Cool / Tech-inspired</td>
							<td>Cool</td>
							<td>Slightly bluish, good for code-heavy documentation</td>
						</tr>
						<tr>
							<td>zinc</td>
							<td>Metallic / Minimalist</td>
							<td>Cool-gray</td>
							<td>Very neutral, suitable for UI component documentation</td>
						</tr>
						<tr>
							<td>neutral</td>
							<td>Pure neutral</td>
							<td>Extremely neutral</td>
							<td>Slightly grayish-yellow, visually the softest</td>
						</tr>
						<tr>
							<td>stone</td>
							<td>Natural / Soft</td>
							<td>Warm</td>
							<td>Resembles paper or stone, with a natural, vintage feel</td>
						</tr>
					</tbody>
				</table>
 
				<h2>Why do they all seem so similar?</h2>
				<p>
					The shared background color and smaller font size can mask the subtle color differences in details like blockquotes, list markers, code blocks, and table borders. But they do have
					subtle differences across themes.
				</p>
			</section>
 
			{/* Grayscale theme demonstration area */}
			<div className="flex flex-row space-x-2">
				{configurations.map((config, index) => (
					<div
						key={index}
						className={`w-1/5 prose dark:prose-invert p-2 rounded-lg border border-gray-300 shadow-sm ${config.proseClass}`}
						dangerouslySetInnerHTML={{
							__html: generateHtmlCode(config.proseClass, config.title, config.subtitle),
						}}
					/>
				))}
			</div>
		</div>
	)
}

Then, in the browser, visit http://localhost:3000/4-gray-scale to see the effect. The grayscale typography theme is designed to match the gray tones of different design languages, but it's not easy to distinguish in the visual effect.

Dark Mode Support

  • Each default theme includes a carefully designed dark mode version. If you're testing locally and it's dark outside, and your system switches to dark mode, you'll notice that the visual effect is poor. Don't worry, we can simply add a dark:prose-invert to each place where prose is used. However, it's best to configure a dark mode for custom adjustments or themes as well.
  • If you don't want to use dark mode, the simplest way is to modify the code in globals.css to adjust the foreground and background colors.
@media (prefers-color-scheme: dark) {
 
:root {
@media (prefers-color-scheme: dark) {
  :root {
    /*--background: #0a0a0a;
    --foreground: #ededed; */
    --background: #ffffff;
    --foreground: #171717;
  }
}
  • Remove dark:prose-invert

How to Override the Max-Width Limit

Each size modifier includes a built-in max-width to maintain content readability. If you want the content to completely fill the width of its container, you just need to add max-w-none to your content to override the built-in max-width.

In the app directory, create a 5-override-max-with directory, create a page.tsx file in that directory, and copy the following code:

export default function OverrideMaxWidth() {
	return (
		<div className="flex flex-col space-y-8 p-8">
			<div className="prose dark:prose-invert">
				<h1>Default Behavior</h1>
				<p>
					Overriding Max Width: Every size modifier includes a built-in `max-width` to maintain content readability. However, this may not always be the desired effect, as sometimes you
					might want the content to fill its container's full width. In these cases, you just need to add `max-w-none` to your content to override the built-in max-width. Summary: The
					default behavior of the `prose` class (e.g., `prose-lg`, `prose-xl`, etc.) in Tailwind CSS is to apply a `max-width` constraint. The purpose of this default `max-width` is to
					improve the readability of text content by preventing lines from becoming too long. The need to override arises when you want the content to fill the full available width of its
					parent container. The solution is simple: to make the content fill the container completely, just add the `max-w-none` Tailwind CSS utility class to the element that already has
					the `prose` class. The `max-w-none` class removes the default maximum width constraint set by `prose`.
				</p>
			</div>
			<div className="prose dark:prose-invert max-w-none">
				<h1>Override Max Width</h1>
				<p>
					Overriding Max Width: Every size modifier includes a built-in `max-width` to maintain content readability. However, this may not always be the desired effect, as sometimes you
					might want the content to fill its container's full width. In these cases, you just need to add `max-w-none` to your content to override the built-in max-width. Summary: The
					default behavior of the `prose` class (e.g., `prose-lg`, `prose-xl`, etc.) in Tailwind CSS is to apply a `max-width` constraint. The purpose of this default `max-width` is to
					improve the readability of text content by preventing lines from becoming too long. The need to override arises when you want the content to fill the full available width of its
					parent container. The solution is simple: to make the content fill the container completely, just add the `max-w-none` Tailwind CSS utility class to the element that already has
					the `prose` class. The `max-w-none` class removes the default maximum width constraint set by `prose`.
				</p>
			</div>
		</div>
	)
}

In the browser, visit http://localhost:3000/5-override-max-width to see the actual effect.

How to Undo Prose Styles

If you don't want certain content to apply the prose style, you can undo the typography style by adding the not-prose class to the element.

In the app directory, create a 6-undo-style directory, create a page.tsx file in that directory, and copy the following code:

export default function UndoStyle() {
	return (
		<div className="flex flex-row space-x-6 items-start justify-center h-screen w-screen p-8">
			<div className="prose dark:prose-invert">
				<h1>✅ With Typography</h1>
				<h2>H2: Section Heading</h2>
				<h3>H3: Subsection Heading</h3>
				<p>This is a paragraph to demonstrate typography styles, showing the visual effects of line height, letter spacing, and paragraph spacing.</p>
				<p>
					You can write text that is <span style={{ fontWeight: "bold" }}>bold</span>, <em>italicized</em>, or contains <code>code snippets</code>.
				</p>
				<blockquote>This is a blockquote area.</blockquote>
				<ul>
					<li>Unordered List Item A</li>
					<li>Unordered List Item B</li>
				</ul>
				<ol>
					<li>Ordered List 1</li>
					<li>Ordered List 2</li>
				</ol>
				<pre>
					<code>{`function greet(name) {
  return 'Hello, ' + name + '!';
}`}</code>
				</pre>
				<table>
					<thead>
						<tr>
							<th>Item</th>
							<th>Description</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>HTML</td>
							<td>Hypertext Markup Language</td>
						</tr>
						<tr>
							<td>CSS</td>
							<td>Cascading Style Sheets</td>
						</tr>
					</tbody>
				</table>
			</div>
 
			<div className="prose dark:prose-invert">
				<h1 className="not-prose">Undo Some Typography Styles</h1>
				<h2>H2: not-prose</h2>
				<h3>H3: Subsection Heading</h3>
				<p>This is a paragraph to demonstrate typography styles, showing the visual effects of line height, letter spacing, and paragraph spacing.</p>
				<p>
					You can write text that is <span style={{ fontWeight: "bold" }}>bold</span>, <em>italicized</em>, or contains <code>code snippets</code>.
				</p>
				<blockquote>This is a blockquote area.</blockquote>
				<ul className="not-prose">
					<li>Unordered List Item A</li>
					<li>Unordered List Item B</li>
					<li className="text-red-500">Add the `not-prose` class to undo typography styles</li>
				</ul>
				<ol>
					<li>Ordered List 1</li>
					<li>Ordered List 2</li>
				</ol>
				<pre>
					<code>{`function greet(name) {
  return 'Hello, ' + name + '!';
}`}</code>
				</pre>
				<table className="not-prose">
					<thead>
						<tr>
							<th>Item</th>
							<th>Description</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>HTML</td>
							<td>Hypertext Markup Language</td>
						</tr>
						<tr>
							<td>CSS</td>
							<td>Cascading Style Sheets</td>
						</tr>
						<tr>
							<td className="text-red-500">Add the `not-prose` class to undo typography styles</td>
							<td className="text-red-500">Add the `not-prose` class to undo typography styles</td>
						</tr>
					</tbody>
				</table>
			</div>
		</div>
	)
}

In the browser, visit http://localhost:3000/6-undo-style to see the actual effect.

Renaming the Default Prose Class

If you need to modify the default prose name for any reason, you can use the className option when registering the plugin:

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

⚠️ Also, you need to replace all places where prose is used, including the prefix.

Summary

  • The @tailwindcss/typography plugin offers an exceptional typographic experience for content-heavy pages, allowing you to instantly beautify rich text content like articles, blogs, and documentation with a single class. It not only enhances readability and professionalism but also saves developers a significant amount of time on layout and style adjustments.
  • This article guides you through the entire process, from project creation and basic usage to advanced techniques like responsive font sizes, grayscale themes, dark mode, and custom styles, giving you a systematic understanding of how to use the Typography plugin.
  • Whether you're a content creator, a solo developer, or building your own blog or documentation system, Tailwind Typography is an excellent choice for your preferred typographic solution.

📎 Example Project Source Code:

👉 GitHub repository

👉 Live Demo

References

暂无目录