The content section of your tailwind.config.js file is where you configure the paths to all of your HTML templates, JavaScript components, and any other source files that contain Tailwind class names.

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}',
  ],
  // ...
}

This guide covers everything you need to know to make sure Tailwind generates all of the CSS needed for your project.


Configuring source paths

Tailwind CSS works by scanning all of your HTML, JavaScript components, and any other template files for class names, then generating all of the corresponding CSS for those styles.

In order for Tailwind to generate all of the CSS you need, it needs to know about every single file in your project that contains any Tailwind class names.

Configure the paths to all of your content files in the content section of your configuration file:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}'
  ],
  // ...
}

Paths are configured as glob patterns, making it easy to match all of the content files in your project without a ton of configuration:

  • Use * to match anything except slashes and hidden files
  • Use ** to match zero or more directories
  • Use comma separate values between {} to match against a list of options

Tailwind uses the fast-glob library under-the-hood — check out their documentation for other supported pattern features.

Paths are relative to your project root, not your tailwind.config.js file, so if your tailwind.config.js file is in a custom location, you should still write your paths relative to the root of your project.

Pattern recommendations

For the best performance and to avoid false positives, be as specific as possible with your content configuration.

If you use a really broad pattern like this one, Tailwind will even scan node_modules for content which is probably not what you want:

Don’t use extremely broad patterns

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './**/*.{html,js}',
  ],
  // ...
}

If you have any files you need to scan that are at the root of your project (often an index.html file), list that file independently so your other patterns can be more specific:

Be specific with your content patterns

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './components/**/*.{html,js}',
    './pages/**/*.{html,js}',
    './index.html',
  ],
  // ...
}

Some frameworks hide their main HTML entry point in a different place than the rest of your templates (often public/index.html), so if you are adding Tailwind classes to that file make sure it’s included in your configuration as well:

Remember to include your HTML entry point if applicable

tailwind.config.js
module.exports = {
  content: [
    './public/index.html',
    './src/**/*.{html,js}',
  ],
  // ...
}

If you have any JavaScript files that manipulate your HTML to add classes, make sure you include those as well:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    // ...
    './src/**/*.js',
  ],
  // ...
}
src/spaghetti.js
// ...
menuButton.addEventListener('click', function () {
  let classList = document.getElementById('nav').classList
  classList.toggle('hidden')
  classList.toggle('block')
})
// ...

It’s also important that you don’t scan any CSS files — configure Tailwind to scan your templates where your class names are being used, never the CSS file that Tailwind is generating.

Never include CSS files in your content configuration

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './src/**/*.css',
  ],
  // ...
}

Class detection in-depth

The way Tailwind scans your source code for classes is intentionally very simple — we don’t actually parse or execute any of your code in the language it’s written in, we just use regular expressions to extract every string that could possibly be a class name.

For example, here’s some HTML with every potential class name string individually highlighted:

<div class="md:flex">
  <div class="md:flex-shrink-0">
    <img class="rounded-lg md:w-56" src="/img/shopping.jpg" alt="Woman paying for a purchase">
  </div>
  <div class="mt-4 md:mt-0 md:ml-6">
    <div class="uppercase tracking-wide text-sm text-indigo-600 font-bold">
      Marketing
    </div>
    <a href="/get-started" class="block mt-1 text-lg leading-tight font-semibold text-gray-900 hover:underline">
      Finding customers for your new business
    </a>
    <p class="mt-2 text-gray-600">
      Getting a new business off the ground is a lot of hard work.
      Here are five ideas you can use to find your first customers.
    </p>
  </div>
</div>

We don’t just limit our search to class="..." attributes because you could be using classes anywhere, like in some JavaScript for toggling a menu:

spaghetti.js
<script>
  menuButton.addEventListener('click', function () {
    let classList = document.getElementById('nav').classList
    classList.toggle('hidden')
    classList.toggle('block')
  })
</script>

By using this very simple approach, Tailwind works extremely reliably with any programming language, like JSX for example:

Button.jsx
const sizes = {
  md: 'px-4 py-2 rounded-md text-base',
  lg: 'px-5 py-3 rounded-lg text-lg',
}

const colors = {
  indigo: 'bg-indigo-500 hover:bg-indigo-600 text-white',
  cyan: 'bg-cyan-600 hover:bg-cyan-700 text-white',
}

export default function Button({ color, size, children }) {
  let colorClasses = colors[color]
  let sizeClasses = sizes[size]

  return (
    <button type="button" className={`font-bold ${sizeClasses} ${colorClasses}`}>
      {children}
    </button>
  )
}

Dynamic class names

The most important implication of how Tailwind extracts class names is that it will only find classes that exist as complete unbroken strings in your source files.

If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS:

Don’t construct class names dynamically

<div class="text-{{ error ? 'red' : 'green' }}-600"></div>

In the example above, the strings text-red-600 and text-green-600 do not exist, so Tailwind will not generate those classes.

Instead, make sure any class names you’re using exist in full:

Always use complete class names

<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>

If you’re using a component library like React or Vue, this means you shouldn’t use props to dynamically construct classes:

Don’t use props to build class names dynamically

function Button({ color, children }) {
  return (
    <button className={`bg-${color}-600 hover:bg-${color}-500 ...`}>
      {children}
    </button>
  )
}

Instead, map props to complete class names that are statically detectable at build-time:

Always map props to static class names

function Button({ color, children }) {
  const colorVariants = {
    blue: 'bg-blue-600 hover:bg-blue-500',
    red: 'bg-red-600 hover:bg-red-500',
  }

  return (
    <button className={`${colorVariants[color]} ...`}>
      {children}
    </button>
  )
}

This has the added benefit of letting you map different prop values to different color shades for example:

function Button({ color, children }) {
  const colorVariants = {
    blue: 'bg-blue-600 hover:bg-blue-500 text-white',
    red: 'bg-red-500 hover:bg-red-400 text-white',
    yellow: 'bg-yellow-300 hover:bg-yellow-400 text-black',
  }

  return (
    <button className={`${colorVariants[color]} ...`}>
      {children}
    </button>
  )
}

As long as you always use complete class names in your code, Tailwind will generate all of your CSS perfectly every time.

Working with third-party libraries

If you’re working with any third-party libraries (for example Select2) and styling that library with your own custom CSS, we recommend writing those styles without using Tailwind’s @layer feature:

main.css
@tailwind base;
@tailwind components;

.select2-dropdown {
  @apply rounded-b-lg shadow-md;
}
.select2-search {
  @apply border border-gray-300 rounded;
}
.select2-results__group {
  @apply text-lg font-bold text-gray-900;
}
/* ... */

@tailwind utilities;

This will ensure that Tailwind always includes those styles in your CSS, which is a lot easier than configuring Tailwind to scan the source code of a third-party library.

If you’ve created your own reusable set of components that are styled with Tailwind and are importing them in multiple projects, make sure to configure Tailwind to scan those components for class names:

tailwind.config.js
module.exports = {
  content: [
    './components/**/*.{html,js}',
    './pages/**/*.{html,js}',
    './node_modules/@my-company/tailwind-components/**/*.js',
  ],
  // ...
}

This will make sure Tailwind generates all of the CSS needed for those components as well.

If you’re working in a monorepo with workspaces, you may need to use require.resolve to make sure Tailwind can see your content files:

tailwind.config.js
const path = require('path');

module.exports = {
  content: [
    './components/**/*.{html,js}',
    './pages/**/*.{html,js}',
    path.join(path.dirname(require.resolve('@my-company/tailwind-components')), '**/*.js'),
  ],
  // ...
}

Using relative paths

By default Tailwind resolves non-absolute content paths relative to the current working directory, not the tailwind.config.js file. This can lead to unexpected results if you run Tailwind from a different directory.

To always resolve paths relative to the tailwind.config.js file, use the object notation for your content configuration and set the relative property to true:

tailwind.config.js
module.exports = {
  content: {
    relative: true,
    files: [
      './pages/**/*.{html,js}',
      './components/**/*.{html,js}',
    ],
  },
  // ...
}

This will likely become the default behavior in the next major version of the framework.

Configuring raw content

If for whatever reason you need to configure Tailwind to scan some raw content rather than the contents of a file, use an object with a raw key instead of a path:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}',
    { raw: '<div class="font-bold">', extension: 'html' },
  ],
  // ...
}

There aren’t many valid use-cases for this — safelisting is usually what you really want instead.


Safelisting classes

For the smallest file size and best development experience, we highly recommend relying on your content configuration to tell Tailwind which classes to generate as much as possible.

Safelisting is a last-resort, and should only be used in situations where it’s impossible to scan certain content for class names. These situations are rare, and you should almost never need this feature.

If you need to make sure Tailwind generates certain class names that don’t exist in your content files, use the safelist option:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}',
  ],
  safelist: [
    'bg-red-500',
    'text-3xl',
    'lg:text-4xl',
  ]
  // ...
}

One example of where this can be useful is if your site displays user-generated content and you want users to be able to use a constrained set of Tailwind classes in their content that might not exist in your own site’s source files.

Using regular expressions

Tailwind supports pattern-based safelisting for situations where you need to safelist a lot of classes:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}',
  ],
  safelist: [
    'text-2xl',
    'text-3xl',
    {
      pattern: /bg-(red|green|blue)-(100|200|300)/,
    },
  ],
  // ...
}

Patterns can only match against base utility names like /bg-red-.+/, and won’t match if the pattern includes a variant modifier like /hover:bg-red-.+/.

If you want to force Tailwind to generate variants for any matched classes, include them using the variants option:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}',
  ],
  safelist: [
    'text-2xl',
    'text-3xl',
    {
      pattern: /bg-(red|green|blue)-(100|200|300)/,
      variants: ['lg', 'hover', 'focus', 'lg:hover'],
    },
  ],
  // ...
}

Discarding classes

Since Tailwind uses a very simple approach to detecting class names in your content, you may find that some classes are being generated that you don’t actually need.

For example, this HTML would still generate the container class, even though that class is not actually being used:

<div class="text-lg leading-8 text-gray-600">
  Every custom pool we design starts as a used shipping container, and is
  retrofitted with state of the art technology and finishes to turn it into
  a beautiful and functional way to entertain your guests all summer long.
</div>

You may also want to prevent Tailwind from generating certain classes when those classes would conflict with some existing CSS, but you don’t want to go so far as to prefix all of your Tailwind classes.

In these situations, you can use the blocklist option to tell Tailwind to ignore specific classes that it detects in your content:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}',
  ],
  blocklist: [
    'container',
    'collapse',
  ],
  // ...
}

The blocklist option only affects CSS that would be generated by Tailwind, not custom CSS you’ve authored yourself or are importing from another library.

Unlike safelist, the blocklist option only supports strings, and you cannot block classes using regular expressions.


Transforming source files

If you’re authoring content in a format that compiles to HTML (like Markdown), it often makes sense to compile that content to HTML before scanning it for class names.

Use the content.transform option to transform any content matching a specific file extension before extracting classes:

tailwind.config.js
const remark = require('remark')

module.exports = {
  content: {
    files: ['./src/**/*.{html,md}'],
    transform: {
      md: (content) => {
        return remark().process(content)
      }
    }
  },
  // ...
}

When using content.transform, you’ll need to provide your source paths using content.files instead of as a top-level array under content.


Customizing extraction logic

Use the extract option to override the logic Tailwind uses to detect class names for specific file extensions:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: {
    files: ['./src/**/*.{html,wtf}'],
    extract: {
      wtf: (content) => {
        return content.match(/[^<>"'`\s]*/g)
      }
    }
  },
  // ...
}

This is an advanced feature and most users won’t need it — the default extraction logic in Tailwind works extremely well for almost all projects.

As with transforming, when using content.extract, you’ll need to provide your source paths using content.files instead of as a top-level array under content.


Troubleshooting

Classes aren’t generated

If Tailwind isn’t generating classes, make sure your content configuration is correct and matches all of the right source files.

A common mistake is missing a file extension, for example if you’re using jsx instead of js for your React components:

tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{html,js}',
    './src/**/*.{html,js,jsx}'
  ],
  // ...
}

Or creating a new folder mid-project that wasn’t covered originally and forgetting to add it to your configuration:

tailwind.config.js
module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}',
    './util/**/*.{html,js}'
  ],
  // ...
}

It could also be that you are trying to use dynamic class names, which won’t work because Tailwind doesn’t actually evaluate your source code and can only detect static unbroken class strings.

Don’t construct class names dynamically

<div class="text-{{ error ? 'red' : 'green' }}-600"></div>

Make sure you always use complete class names in your code:

Always use complete class names

<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>

Read our documentation on dynamic class names for more details.

Styles rebuild in an infinite loop

If your CSS seems to be rebuilding in an infinite loop, there’s a good chance it’s because your build tool doesn’t support the glob option when registering PostCSS dependencies.

Many build tools (such as webpack) don’t support this option, and as a result we can only tell them to watch specific files or entire directories. We can’t tell webpack to only watch *.html files in a directory for example.

That means that if building your CSS causes any files in those directories to change, a rebuild will be triggered, even if the changed file doesn’t match the extension in your glob.

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    // With some build tools, your CSS will rebuild
    // any time *any* file in `src` changes.
    './src/**/*.{html,js}',
  ],
  // ...
}

So if you are watching src/**/*.html for changes, but you are writing your CSS output file to src/css/styles.css, you will get an infinite rebuild loop using some tools.

Ideally we could warn you about this in the console, but many tools support it perfectly fine (including our own CLI tool), and we have no reliable way to detect what build tool you are using.

To solve this problem, use more specific paths in your content config, making sure to only include directories that won’t change when your CSS builds:

tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{html,js}',
    './src/pages/**/*.{html,js}',
    './src/components/**/*.{html,js}',
    './src/layouts/**/*.{html,js}',
    './src/index.html',
  ],
  // ...
}

If necessary, adjust your actual project directory structure to make sure you can target your template files without accidentally catching your CSS file or other build artifacts like manifest files.

If you absolutely can’t change your content config or directory structure, your best bet is to compile your CSS separately with a tool that has complete glob support. We recommend using Tailwind CLI, which is a fast, simple, purpose-built tool for compiling your CSS with Tailwind.

It just isn’t working properly

If you are experiencing weird, hard to describe issues with the output, or things just don’t seem like they are working at all, there’s a good chance it’s due to your build tool not supporting PostCSS dependency messages properly (or at all). One known example of this currently is Stencil.

When you are having these sorts of issues, we recommend using Tailwind CLI to compile your CSS separately instead of trying to integrate Tailwind into your existing tooling.

You can use packages like npm-run-all or concurrently to compile your CSS alongside your usual development command by adding some scripts to your project like this:

// package.json
{
  // ...
  "scripts": {
    "start": "concurrently \"npm run start:css\" \"react-scripts start\"",
    "start:css": "tailwindcss -o src/tailwind.css --watch",
    "build": "npm run build:css && react-scripts build",
    "build:css": "NODE_ENV=production tailwindcss -o src/tailwind.css -m",
  },
}

Either way, please be sure to check for an existing issue or open a new one so we can figure out the problem and try to improve compatibility with whatever tool you are using.