The best way to add TailwindCSS to an Astro project (including support for PostCSS plugins!)

Kevin Firko

The best way to to add TailwindCSS to an Astro project is to install and configure tailwind and postcss independently without the official @astrojs/tailwind integration.

I’ve found the integration doesn’t make adding tailwind any faster or easier meanwhile it imposes unnecessary restrictions and can create new problems that simply don’t need to exist!

A huge advantage to fully owning your project’s postcss configuration is the ability to leverage powerful plugins to optimize builds and enhance workflows.

Here’s my step-by-step guide for adding TailwindCSS to Astro.

I included an extra section at the end covers how to add PostCSS plugins.

Step-by-Step Guide

All examples use TypeScript, pnpm, and ESM (module) imports. You may need to make adjustments if and where your project differs.

Step 1 — Add dependencies

In an Astro project add the dev dependencies tailwindcss, postcss, and autoprefixer:

pnpm add -D tailwindcss postcss autoprefixer

Step 2 — Add tailwind config

Add a config file for tailwind: tailwind.config.ts.

The following is an example of a basic config without any tailwind plugins or theme customizations:

import type { Config } from 'tailwindcss'

const tailwindConfig = {
  darkMode: 'class',
  content: {
    files: ['./src/**/*!(*.stories|*.spec|*.test).{ts,tsx,astro,md,mdx,html}'],
  theme: {
    extend: {},
  plugins: [],
} satisfies Config

export default tailwindConfig

It is extremely important to make sure that the path pattern(s) in the content.files array match all files in your project that could use tailwindcss utility classes.

During a build tailwind only outputs css that corresponds to utilities that you actually use in your code and it only inspects files that match the given patterns.

If your project uses tailwind utilities in files with js, jsx, or other extensions such as vue or svelte then be sure to include those in the array as well.

If you have a monorepo with internal packages used by your Astro app and they use tailwind utilities then you may need to add path patterns for those source files too.

Learn more from the official docs:

Step 3 — Add a stylesheet with tailwind directives

Add a css stylesheet that includes tailwind’s base, component, and utility directives at the top of the file.

For example src/styles/tailwind.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

These directives are placeholders where tailwind will inject css during build.

It doesn’t matter what you name your stylesheet or where you save it as long as it makes sense for your project. Filenames like main.css, style.css, and others work great too.

Step 4 — Import your stylesheet in a layout

Import your stylesheet at top of your base Astro layout file(s).

I usually create a top-level layout file in my projects called BaseLayout (e.g. src/layouts/BaseLayout.astro) and I then use that as the base for all other layouts in my project.

Import the stylesheet at the top of your layout before other imports:

import '../styles/tailwind.css'

The reason to import the css file first is so the stylesheet is loaded before any other components styled with tailwind utilities.

Step 5 — Update your Astro config

In astro.config.ts (or *.js or *.mjs) import the required PostCSS plugins:

import tailwindcss from 'tailwindcss'
import tailwindcssNesting from 'tailwindcss/nesting'
import autoprefixer from 'autoprefixer'

Astro uses Vite under the hood to build projects including CSS via PostCSS.

Inside the defineConfig({...}) block ensure vite.css.postss.plugins: [...] is present and add the plugins to the array:

export default defineConfig({
  // ...
  vite: {
    // ...
    css: {
      postcss: {
        plugins: [
          tailwindcss({ config: path.resolve(import.meta.dirname, 'tailwind.config.ts') }),
    // ...
  // ...

That’s it! You’re done!


Advanced users should note that Vite ignores any postcss.config.{js,ts} config files when a postcss configuration is specified inline inside a config file as we do above.

It is not always necessary to specify an explicit config path for tailwind in the tailwindcss({...}) options however I have found that it helps to avoid certain hiccups when using a TypeScript-based config file and when working in a monorepo.

import.meta.dirname in the above example only exists in Node20+ with ESM and is equivalent to CommonJS __dirname. If you are running Node20+ and TypeScript shows an error then add @types/node to your dev dependencies.

Extra Section: PostCSS Plugins

A key benefit of setting up tailwindcss on your own vs. using the @astrojs/tailwind integration is the ability to fully control postcss and manage plugins without issues.

PostCSS has a large ecosystem of capable plugins to optimize project builds and enhance developer workflows.

To its credit the @astrojs/tailwind integration attempts to play nice with postcss configs however in the end it is third-party code that mucks with the expected behaviour of Astro + Vite + PostCSS and this inevitably leads to issues.

In this extra section I’ll show you how to add two postcss plugins that I like to include in Astro projects.

You can follow a similar process to install and configure any PostCSS plugin.

Introducing the Plugins

postcss-discard-comments automatically wipes any comments from CSS files for smaller builds and shorter load times. The plugin preserves any comments marked ! important by default and this behaviour can be customized by specifying options covered in its docs.

@csstools/postcss-oklab-function enables the use of the newer oklab and oklch color functions in CSS per the CSS Color Specification. It generates fallbacks for older browsers that do not support oklab/oklch.

Step 1 — Install the plugins

Add the plugins as dev dependencies:

pnpm add -D postcss-discard-comments
pnpm add -D @csstools/postcss-oklab-function

Step 2 — Add the plugins to your config

Next add the plugins to the inline postcss configuration within the Astro config at astro.config.{ts,js,mjs}.

Start by adding imports for the plugins towards the top of the config file:

import cssDiscardComments from 'postcss-discard-comments'
import postCssOklabPolyfill from '@csstools/postcss-oklab-function'

Finish by adding the plugins to the vite.css.postcss.plugins array inside the defineConfig({...}) block.

  // ...
    css: {
      postcss: {
        plugins: [
          tailwindcss({ config: path.resolve(import.meta.dirname, 'tailwind.config.ts') }),
          postCssOklabPolyfill({ preserve: true }),
          cssDiscardComments({ removeAll: true }),
  // ...

All done!