NEWVersion 1.0 is here.
OpenTailwindv1.0
BlocksTemplatesCollectionsGuidesPricing
Tailwind CSS Performance Optimization: A v4 Guide
BlogTailwind CSS Performance Optimization: A v4 Guide
GuideOctober 20, 2026·9 min read

Tailwind CSS Performance Optimization: A v4 Guide

How to keep Tailwind CSS v4 sites fast: control source detection, avoid dynamic class names, minify output, fix images, fonts and animations that hurt speed.

By OpenTailwind, by lindo.ai

On this page

  1. How Tailwind v4 builds your CSS
  2. Control source detection
  3. Avoid dynamic class names
  4. Set up a production build
  5. Keep the theme lean
  6. Images, fonts and layout shift
  7. Animations that stay smooth
  8. FAQ

Tailwind CSS v4 already produces small stylesheets because it only generates the utilities your source files use. Most performance work is about making sure it scans the right files, that your build is minified, and that the things around your CSS (images, fonts, animations and scripts) do not slow the page down. This guide walks through each of those in order.

Everything below applies to v4's CSS-first setup: @import "tailwindcss" in your stylesheet, no tailwind.config.js required.

Table of contents

  • How Tailwind v4 builds your CSS
  • Control source detection
  • Avoid dynamic class names
  • Set up a production build
  • Keep the theme lean
  • Images, fonts and layout shift
  • Animations that stay smooth
  • FAQ

How Tailwind v4 builds your CSS

Tailwind scans your project files as plain text, looks for tokens that could be class names, and generates CSS only for the ones that match a utility or variant. There is no separate purge step like in Tailwind v2. If bg-indigo-600 appears nowhere in your source, it is not in the output.

Version 4 also changed how the work happens. The engine was rewritten (the project calls it Oxide), Lightning CSS handles vendor prefixing and syntax lowering, and builds are incremental in watch mode. You get these by using one of the official integrations:

  • @tailwindcss/vite for Vite-based projects
  • @tailwindcss/postcss for Next.js, webpack and other PostCSS setups
  • @tailwindcss/cli for a standalone build with no bundler

The browser build (@tailwindcss/browser) compiles classes at runtime in the page. It is useful for prototypes and demos, but it ships a JavaScript compiler to every visitor, so do not use it in production.

Control source detection

v4 finds source files automatically. It skips files listed in .gitignore, binary files, CSS files and lock files. For most projects that is enough, but two situations need adjustment.

Add files Tailwind cannot see

If your markup lives in a package inside node_modules or in a folder that is gitignored, register it with @source. Paths are relative to the stylesheet.

@import "tailwindcss";

@source "../node_modules/@acme/ui/dist";
@source "../content/blocks";

Exclude files that add noise

Large folders full of text, such as generated docs, fixtures, or a CMS export, can add unused utilities if they happen to contain words that match class names. Exclude them with @source not:

@import "tailwindcss";

@source not "../fixtures";
@source not "../docs/generated";

For full control, turn off automatic detection and list sources yourself. You can also set a base path for detection:

/* Only scan what you list */
@import "tailwindcss" source(none);
@source "../src/components";
@source "../src/app";

/* Or scan a single directory automatically */
@import "tailwindcss" source("../src");

In a monorepo this is often the biggest win, because a stylesheet in one app should not pick up class names from every other app in the repository.

Avoid dynamic class names

Because Tailwind reads files as text, it cannot see class names that are assembled at runtime. This is the most common reason a style "works in development but not in production," and the usual workaround (adding everything to a safelist) increases CSS size.

<!-- Tailwind cannot detect these -->
<div class="bg-{{ color }}-600">...</div>
<div class="text-{{ size }}">...</div>

<!-- Map props to complete class names instead -->
<!-- e.g. { primary: "bg-indigo-600", danger: "bg-red-600" } -->
<div class="bg-indigo-600">...</div>

Keep a lookup object of full class strings in your component code and pick from it. Every class appears in full somewhere in the source, so detection works and nothing extra is generated.

When you do need classes that never appear in source, for example classes stored in a database, v4 replaces the old safelist option with @source inline(). It supports brace expansion, so keep the list as narrow as you can:

@import "tailwindcss";

@source inline("{hover:,}bg-{indigo,emerald}-{500,600}");

Set up a production build

A production stylesheet should be minified and served with compression and long cache headers.

# CLI build for production
npx @tailwindcss/cli -i ./src/input.css -o ./dist/output.css --minify

With Vite or Next.js, production builds minify CSS by default, so the main job is to check what ships. Open your built CSS file and look for surprises: utilities you never wrote, very long selector lists, or duplicated @font-face rules. Those usually point to a source detection issue or a stylesheet imported in more than one entry point.

Other build checklist items:

  • Import Tailwind once. Multiple @import "tailwindcss" statements in separate entry files produce duplicate base styles.
  • Serve CSS with Brotli or gzip compression. Utility CSS has a lot of repetition and compresses well.
  • Use hashed filenames so you can set long cache lifetimes.
  • Load the main stylesheet with a normal <link rel="stylesheet"> in the <head>. Tailwind output is small enough that splitting critical CSS by hand is rarely worth the complexity.

Keep the theme lean

In v4 your design tokens live in CSS with @theme. Each token creates utilities and a CSS custom property. By default only the variables your code uses are emitted, so adding tokens does not grow the output until you use them.

@import "tailwindcss";

@theme {
  --color-brand-600: oklch(0.51 0.23 277);
  --font-display: "Inter", ui-sans-serif, system-ui, sans-serif;
  --breakpoint-3xl: 120rem;
}

A few habits keep things tidy:

  • If you are building a brand palette, reset the default colors with --color-*: initial; so stray default shades in your markup are easy to spot.
  • Use @theme static only when you need every variable available at runtime, for example for JavaScript that reads them. It forces all variables into the output.
  • Prefer @utility for reusable custom utilities instead of large @apply blocks. Custom utilities created with @utility work with variants and are only generated when used.
@utility content-auto {
  content-visibility: auto;
  contain-intrinsic-size: auto 600px;
}

Images, fonts and layout shift

On most landing pages, the stylesheet is a small part of the page weight. Images and fonts usually matter more for Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS).

Images

<!-- Hero image: load early -->
<img src="/hero.avif" alt="Team reviewing a dashboard" width="1600" height="900"
     fetchpriority="high" class="aspect-video w-full rounded-2xl object-cover" />

<!-- Below the fold: load lazily -->
<img src="/feature.avif" alt="Report export screen" width="800" height="600"
     loading="lazy" decoding="async" class="aspect-4/3 w-full rounded-xl object-cover" />
  • Set width and height or an aspect ratio class like aspect-video so the browser reserves space and the page does not shift.
  • Do not lazy-load the LCP image. Give it fetchpriority="high" instead.
  • Serve AVIF or WebP at the size it is displayed, with srcset for different widths.
Analytics dashboard hero section
Hero sections with large images are where LCP is decided — View & copy code

Fonts

Self-host one or two font families, use WOFF2, add font-display: swap, and preload the file used above the fold. Register the family as --font-display inside @theme, as in the example above, and apply it with the font-display utility. Each extra weight is another request, so check which weights the design actually uses.

Long pages

Landing pages built from many sections can benefit from content-visibility: auto on sections below the fold, which lets the browser skip rendering work until they are near the viewport. The content-auto utility above, or an arbitrary property like [content-visibility:auto], applies it. Give it a reasonable contain-intrinsic-size so the scrollbar does not jump.

Feature section with image grid
Image-heavy sections below the fold should lazy-load — View & copy code

Animations that stay smooth

Animations affect responsiveness more than load time. Two rules cover most cases:

  • Animate transform and opacity. Classes like translate-y-4, scale-95 and opacity-0 are cheap for the browser to animate. Animating width, height, top or margin triggers layout on every frame.
  • Use transition-transform or transition-opacity instead of transition-all when you only change one property.
<div class="translate-y-4 opacity-0 transition duration-500 ease-out data-[visible]:translate-y-0 data-[visible]:opacity-100 motion-reduce:transition-none">
  ...
</div>

Use will-change-transform sparingly, only on elements that animate continuously, such as a marquee. Applying it to dozens of elements uses extra memory. For scroll-triggered effects, use a single IntersectionObserver that sets a data attribute, rather than a scroll event listener.

OpenTailwind blocks follow this pattern, with dark mode and scroll animations built from standard classes. You can compare approaches across the stats and hero categories, or start from a full page in the technology and SaaS templates.

Stats section with accent cards
Stats section with accent cards — View & copy code

FAQ

Do I need PurgeCSS with Tailwind v4?

No. Tailwind generates only the classes it finds in your source files. PurgeCSS was common with Tailwind v1 and is not needed now.

Why is a class missing from my production build?

Usually the class name is built dynamically, or the file that uses it is not scanned (for example, it is gitignored or in node_modules). Write the full class name in source, or add the path with @source.

Is @apply bad for performance?

Not by itself, but large @apply blocks duplicate declarations that utilities would share. Use components in your framework or @utility for repeated patterns.

How do I measure the result?

Use Lighthouse or PageSpeed Insights for lab data and the Chrome User Experience Report or your own real user monitoring for field data. Check LCP, CLS and Interaction to Next Paint, not only the CSS file size.

Tailwind v4 handles most CSS optimization for you, as long as it scans the right files and you write class names in full. After that, focus on the parts of the page that weigh more: properly sized images with reserved space, a small set of self-hosted fonts, and animations that stick to transforms and opacity. Check the built CSS once per release and measure with real metrics rather than guessing.

Library access: Tutorials and their educational demos are public. Copying library designs requires an account: 100 selected blocks and five templates per industry are free. Pro lifetime access unlocks the complete library.

Browse 1,761 Tailwind CSS blocks

100 free blocks, five free templates per industry, and Pro lifetime access to the complete block and template library.

Browse BlocksTemplates
← Previous article

Tailwind CSS Grid: A Practical Layout Guide for v4

Next article →

Tailwind CSS Testimonial Section Examples (Free Code)

OpenTailwindv1.0

A little less starting from scratch.
A lot more making it yours.

Made with care by lindo.ai

The library

All blocksAll templatesHero sectionsPricing sections

Around here

Our storyGuides & ideasDesign examplesMembership & pricingQuestions?

Good design is a
good place to start.

FREE TO EXPLORE. YOURS TO BUILD.
© 2026 OpenTailwind · by lindo.ai
TermsPrivacyCookies All creativity welcome