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.
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
- Ordered list 1
- Ordered list 2
function greet(name) {
return 'Hello, ' + name + '!';
}
Item | Description |
---|---|
HTML | Hypertext Markup Language |
CSS | Cascading Style Sheets |
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
- Ordered list 1
- Ordered list 2
function greet(name) {
return 'Hello, ' + name + '!';
}
Item | Description |
---|---|
HTML | Hypertext Markup Language |
CSS | Cascading 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.
- This is especially useful for scenarios where Markdown is rendered to
- 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.
- You can view the complete example on the GitHub repository.
- Check out the live demo on Github Pages Demo.
Prerequisites:
- Node.js version 18.18 or higher installed on your machine.
- pnpm installed.
Create the Application
-
First, create a
Next.js
application usingpnpm
as the package manager, which is the official recommended method forNext.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
- Install
@tailwindcss/typography
, we input the following command to install thetypography
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 theapp
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 theapp
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 withwidth=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 prefix | Minimum width | CSS |
---|---|---|
sm | 40rem (640px) | @media (width >= 40rem) { ... } |
md | 48rem (768px) | @media (width >= 48rem) { ... } |
lg | 64rem (1024px) | @media (width >= 64rem) { ... } |
xl | 80rem (1280px) | @media (width >= 80rem) { ... } |
2xl | 96rem (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
.
Class | Body font size |
---|---|
prose-sm | 0.875rem (14px) |
prose-base (default) | 1rem (16px) |
prose-lg | 1.125rem (18px) |
prose-xl | 1.25rem (20px) |
prose-2xl | 1.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 inHTML
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:
Modifier | Target |
---|---|
prose-headings:{utility} | h1 , h2 , h3 , h4 , th |
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 theapp
directory, andtailwind.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 becomesbackgroundColor
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 ofh1
in the built-inprose-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 passprose: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
andtypography
are already imported inglobals.css
, adding custom configurations to that file would affect global styles. To demonstrate the functionality, we'll add this separate file and re-importtailwindcss
andtypography
. In a production environment, you should avoid this practice. TheNext.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
andcustom.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 whereprose
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: