---
meta:
  nav: TailwindCSS
  title: Vuetify + TailwindCSS
  description: Integrate TailwindCSS v4 into an existing Vuetify project using Vite or Nuxt.
  keywords: tailwindcss, tailwindcss v4, vuetify tailwind, css layers, vite, nuxt
related:
  - /features/css-utilities/
  - /styles/layers/
  - /features/sass-variables/
---

# TailwindCSS

Integrate TailwindCSS v4 into an existing Vuetify project for a smaller CSS bundle, on-demand utility generation, and variants like `hover:`, `dark:`, and responsive prefixes.

<PageFeatures />

<PromotedEntry />

---

:::: tabs

```bash [npx]
# generate working project for reference
npx @vuetify/cli@latest init --type=vuetify --css=tailwindcss
```

```bash [pnpm]
# generate working project for reference
pnpx @vuetify/cli@latest init --type=vuetify --css=tailwindcss
```

```bash [yarn]
# generate working project for reference
yarn dlx @vuetify/cli@latest init --type=vuetify --css=tailwindcss
```

```bash [bun]
# generate working project for reference
bunx @vuetify/cli@latest init --type=vuetify --css=tailwindcss
```

::::

## Establish CSS layer order

Create a `layers.css` file that declares the cascade layers in order. `tailwind` goes above component styles but below `vuetify-final`, where Vuetify keeps its transitions:

```css
@layer tailwind-theme;
@layer tailwind-reset;

@layer vuetify-core;
@layer vuetify-components;
@layer vuetify-overrides;
@layer vuetify-utilities;

@layer tailwind-utilities;

@layer vuetify-final;
```

This file must be loaded **before** any other styles. In a **Vite** project, save it as `src/styles/layers.css` and import it at the top of `src/plugins/vuetify.ts`, before `vuetify/styles`. You can find the exact configuration snippets in the sections for Vite and Nuxt below.

## Setup dependencies

### Vite

Import the layers file at the top of `src/plugins/vuetify.ts`, before `vuetify/styles`:

```ts { resource="src/plugins/vuetify.ts" }
import '../styles/layers.css'
import 'vuetify/styles'
// ...
```

Install TailwindCSS and the Vite plugin:

::: tabs

```bash [pnpm]
pnpm add -D tailwindcss @tailwindcss/vite
```

```bash [yarn]
yarn add -D tailwindcss @tailwindcss/vite
```

```bash [npm]
npm i -D tailwindcss @tailwindcss/vite
```

```bash [bun]
bun add -D tailwindcss @tailwindcss/vite
```

:::

Register `tailwindcss()` as the **first** entry in `plugins` inside `vite.config.mts`:

```ts { resource="vite.config.mts" }
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    tailwindcss(), // must come before Vuetify
    // ...
  ],
})
```

Import the TailwindCSS stylesheet (see [Configure TailwindCSS](#configure-tailwindcss)) in `src/main.ts`:

```ts { resource="src/main.ts" }
import './styles/tailwind.css'
```

### Nuxt

::: tabs

```bash [pnpm]
pnpm add -D tailwindcss @tailwindcss/postcss
```

```bash [yarn]
yarn add -D tailwindcss @tailwindcss/postcss
```

```bash [npm]
npm i -D tailwindcss @tailwindcss/postcss
```

```bash [bun]
bun add -D tailwindcss @tailwindcss/postcss
```

:::

Register `@tailwindcss/postcss` as a PostCSS plugin in `nuxt.config.ts`. The `css` array controls load order — `layers.css` must come first, followed by `vuetify/styles`, then `tailwind.css`. Set `disableVuetifyStyles: true` — otherwise the module injects styles automatically and the order above is ignored:

```ts { resource="nuxt.config.ts" }
export default defineNuxtConfig({
  modules: [
    'vuetify-nuxt-module',
    // ...
  ],

  css: [
    'assets/styles/layers.css',
    'vuetify/styles',
    'assets/styles/tailwind.css',
  ],

  postcss: {
    plugins: {
      '@tailwindcss/postcss': {},
    },
  },

  vuetify: {
    moduleOptions: {
      disableVuetifyStyles: true,
      styles: { configFile: 'assets/styles/settings.scss' },
    },
  },
})
```

## Configure TailwindCSS

Create `tailwind.css` (in `src/styles/` for Vite or `assets/styles/` for Nuxt). Tailwind's preflight is skipped because Vuetify ships its own reset. The `@custom-variant` declarations wire `dark:` and `light:` prefixes to Vuetify's theme classes, and breakpoints are overridden to match Vuetify's defaults:

```css { resource="tailwind.css" }
@import "tailwindcss/theme" layer(tailwind-theme);
@import "tailwindcss/utilities" layer(tailwind-utilities);

/* dark/light mode — Vuetify uses .v-theme--dark/.v-theme--light instead of .dark */
@custom-variant light (&:where(.v-theme--light, .v-theme--light *));
@custom-variant dark (&:where(.v-theme--dark, .v-theme--dark *));

@theme {
  --breakpoint-*: initial; /* reset Tailwind defaults */
  /* keep in sync with vuetify plugin/config and settings.scss */
  --breakpoint-xs:  0px;
  --breakpoint-sm:  600px;
  --breakpoint-md:  960px;
  --breakpoint-lg:  1280px;
  --breakpoint-xl:  1920px;
  --breakpoint-xxl: 2560px;
}

/*
  note: adopt and extend values from TailwindCSS
*/
@utility rounded-pill   { border-radius: 9999px }
@utility rounded-circle { border-radius: 50%    }
@utility rounded-shaped { border-radius: 24px 0 }

@source inline('rounded'); /* .25rem */
@source inline('rounded-{none,sm,md,lg,xl,2xl,3xl,full,pill,circle,shaped}');

/*
  note: adopt elevation shadows from TailwindCSS
*/
@utility elevation-0 { box-shadow: none }
@utility elevation-1 { box-shadow: var(--shadow-xs) }
@utility elevation-2 { box-shadow: var(--shadow-sm) }
@utility elevation-3 { box-shadow: var(--shadow-md) }
@utility elevation-4 { box-shadow: var(--shadow-xl) }
@utility elevation-5 { box-shadow: var(--shadow-2xl) }

@source inline('elevation-{0,1,2,3,4,5}');
```

## Disable Vuetify's built-in utilities

Turn off Vuetify's built-in utility classes and the Material color palette — TailwindCSS will cover both from here on:

```scss { resource="settings.scss" }
@use 'vuetify/settings' with (
  $color-pack: false,
  $utilities: false,
);
```

After rebuilding you should see the CSS entry file shrink by roughly 150–200 kB (unminified).

## Align breakpoints { id="breakpoints" }

Vuetify and TailwindCSS ship different default breakpoints. Mismatched values cause `sm:` in Tailwind to fire at a different width than `sm` in `VCol` or `useDisplay()`. The `@theme` block in `tailwind.css` above already resets the Tailwind defaults — the same values need to be repeated in two more places.

Vuetify plugin (Vite) or `vuetifyOptions` (Nuxt):

```ts
display: {
  mobileBreakpoint: 'md',
  thresholds: {
    // repeated in tailwind.css and settings.scss
    xs: 0, sm: 600, md: 960, lg: 1280, xl: 1920, xxl: 2560,
  },
},
```

Sass variables:

```scss { resource="settings.scss" }
@use 'vuetify/settings' with (
  $color-pack: false,
  $utilities: false,
  $grid-breakpoints: (
    // repeated in tailwind.css and vuetify config
    'xs': 0,
    'sm': 600px,
    'md': 960px,
    'lg': 1280px,
    'xl': 1920px,
    'xxl': 2560px,
  ),
);
```

::: tip
In a **Nuxt** project you can define breakpoints in a shared TypeScript file and feed them to both Vuetify and the Sass variables from a single source of truth. See the [UnoCSS presetWind4 guide](/features/css-utilities/unocss-tailwind-preset/#breakpoints) for an example of this pattern.
:::

## Dark mode { id="dark-mode" }

The `@custom-variant` declarations in `tailwind.css` above rewire Tailwind's `dark:` and `light:` prefixes to Vuetify's theme selectors. Classes like `dark:bg-sky-900` and `light:text-gray-700` then toggle correctly when switching themes via `$vuetify.theme.cycle()` or programmatically.

::: warning Nuxt SSR and `defaultTheme: 'system'`

Vuetify's `system` theme reads the browser's `prefers-color-scheme` media query at runtime. In a Nuxt project with SSR enabled (the default), this detection runs on the server where no browser preference is available, so the theme always falls back to `light`. Either add `ssr: false` to `nuxt.config.ts`, or start with a fixed default theme:

```ts { resource="nuxt.config.ts" }
theme: {
  defaultTheme: 'dark', // or 'light' — 'system' requires ssr: false
},
```

:::

## Typography { id="typography" }

TailwindCSS provides its own type-scale utilities (`text-sm`, `text-base`, `text-2xl`, etc.) that work well with responsive prefixes. When adopting TailwindCSS, **migrate to this convention** rather than trying to preserve Vuetify's typography classes (`text-h1` through `text-overline` for MD2, or `text-display-large` through `text-label-small` for MD3).

TailwindCSS utilities give you finer control — `text-2xl font-light tracking-tight` lets you mix size, weight, and spacing freely instead of relying on a predefined bundle. The trade-off is that your team needs to agree on which combinations to use, usually enforced through shared components or a design token system.

If you are integrating TailwindCSS into an **existing project** that already uses Vuetify's typography classes extensively, you can define `@utility` rules to keep them working during the migration. Below are drop-in snippets for both the MD2 (legacy) and MD3 (default) typography scales.

<details>
<summary>MD2 typography utilities (text-h1 … text-overline)</summary>

These match Vuetify's MD2 defaults and reference `--font-heading` / `--font-body` CSS custom properties. Define them in your global CSS or in a Sass `@use 'vuetify/settings'` block.

```css { resource="tailwind.css" }
@utility text-h1 {
  font-family: var(--font-heading, inherit);
  font-size: 6rem;
  font-weight: 300;
  line-height: 1;
  letter-spacing: -.015625em;
  text-transform: none;
}
@utility text-h2 {
  font-family: var(--font-heading, inherit);
  font-size: 3.75rem;
  font-weight: 300;
  line-height: 1;
  letter-spacing: -.0083333333em;
  text-transform: none;
}
@utility text-h3 {
  font-family: var(--font-heading, inherit);
  font-size: 3rem;
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: normal;
  text-transform: none;
}
@utility text-h4 {
  font-family: var(--font-heading, inherit);
  font-size: 2.125rem;
  font-weight: 400;
  line-height: 1.175;
  letter-spacing: .0073529412em;
  text-transform: none;
}
@utility text-h5 {
  font-family: var(--font-heading, inherit);
  font-size: 1.5rem;
  font-weight: 400;
  line-height: 1.333;
  letter-spacing: normal;
  text-transform: none;
}
@utility text-h6 {
  font-family: var(--font-heading, inherit);
  font-size: 1.25rem;
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: .0125em;
  text-transform: none;
}
@utility text-subtitle-1 {
  font-family: var(--font-body, inherit);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.75;
  letter-spacing: .009375em;
  text-transform: none;
}
@utility text-subtitle-2 {
  font-family: var(--font-body, inherit);
  font-size: .875rem;
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: .0071428571em;
  text-transform: none;
}
@utility text-body-1 {
  font-family: var(--font-body, inherit);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: .03125em;
  text-transform: none;
}
@utility text-body-2 {
  font-family: var(--font-body, inherit);
  font-size: .875rem;
  font-weight: 400;
  line-height: 1.425;
  letter-spacing: .0178571429em;
  text-transform: none;
}
@utility text-button {
  font-family: var(--font-body, inherit);
  font-size: .875rem;
  font-weight: 500;
  line-height: 2.6;
  letter-spacing: .0892857143em;
  text-transform: uppercase;
}
@utility text-caption {
  font-family: var(--font-body, inherit);
  font-size: .75rem;
  font-weight: 400;
  line-height: 1.667;
  letter-spacing: .0333333333em;
  text-transform: none;
}
@utility text-overline {
  font-family: var(--font-body, inherit);
  font-size: .75rem;
  font-weight: 500;
  line-height: 2.667;
  letter-spacing: .1666666667em;
  text-transform: uppercase;
}
```

</details>

<details>
<summary>MD3 typography utilities (text-display-large … text-label-small)</summary>

These match Vuetify's MD3 defaults (the current default typography scale). None of the MD3 classes use `text-transform`.

```css { resource="tailwind.css" }
@utility text-display-large {
  font-family: var(--font-heading, inherit);
  font-size: 3.5625rem;
  font-weight: 400;
  line-height: 1.1228070175;
  letter-spacing: -.0043859649em;
}
@utility text-display-medium {
  font-family: var(--font-heading, inherit);
  font-size: 2.8125rem;
  font-weight: 400;
  line-height: 1.1555555556;
  letter-spacing: normal;
}
@utility text-display-small {
  font-family: var(--font-heading, inherit);
  font-size: 2.25rem;
  font-weight: 400;
  line-height: 1.2222222222;
  letter-spacing: normal;
}
@utility text-headline-large {
  font-family: var(--font-heading, inherit);
  font-size: 2rem;
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: normal;
}
@utility text-headline-medium {
  font-family: var(--font-heading, inherit);
  font-size: 1.75rem;
  font-weight: 400;
  line-height: 1.2857142857;
  letter-spacing: normal;
}
@utility text-headline-small {
  font-family: var(--font-heading, inherit);
  font-size: 1.5rem;
  font-weight: 400;
  line-height: 1.3333333333;
  letter-spacing: normal;
}
@utility text-title-large {
  font-family: var(--font-heading, inherit);
  font-size: 1.375rem;
  font-weight: 400;
  line-height: 1.2727272727;
  letter-spacing: normal;
}
@utility text-title-medium {
  font-family: var(--font-body, inherit);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: .009375em;
}
@utility text-title-small {
  font-family: var(--font-body, inherit);
  font-size: .875rem;
  font-weight: 500;
  line-height: 1.4285714286;
  letter-spacing: .0071428571em;
}
@utility text-body-large {
  font-family: var(--font-body, inherit);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: .03125em;
}
@utility text-body-medium {
  font-family: var(--font-body, inherit);
  font-size: .875rem;
  font-weight: 400;
  line-height: 1.4285714286;
  letter-spacing: .0178571429em;
}
@utility text-body-small {
  font-family: var(--font-body, inherit);
  font-size: .75rem;
  font-weight: 400;
  line-height: 1.3333333333;
  letter-spacing: .0333333333em;
}
@utility text-label-large {
  font-family: var(--font-body, inherit);
  font-size: .875rem;
  font-weight: 500;
  line-height: 1.4285714286;
  letter-spacing: .0071428571em;
}
@utility text-label-medium {
  font-family: var(--font-body, inherit);
  font-size: .75rem;
  font-weight: 500;
  line-height: 1.3333333333;
  letter-spacing: .0416666667em;
}
@utility text-label-small {
  font-family: var(--font-body, inherit);
  font-size: .6875rem;
  font-weight: 500;
  line-height: 1.4545454545;
  letter-spacing: .0454545455em;
}
```

</details>

## Theme colors { id="theme-colors" }

Vuetify stores theme colors as raw RGB channels in CSS custom properties (e.g. `--v-theme-primary`). Wrapping them in `rgb()` inside a `@theme` block makes them available as standard Tailwind color utilities (`bg-primary`, `text-error`, etc.):

```css { resource="tailwind.css" }
@theme {
  /* ... breakpoints ... */
  --color-background:      rgb(var(--v-theme-background));
  --color-surface:         rgb(var(--v-theme-surface));
  --color-surface-variant: rgb(var(--v-theme-surface-variant));
  --color-primary:         rgb(var(--v-theme-primary));
  --color-success:         rgb(var(--v-theme-success));
  --color-warning:         rgb(var(--v-theme-warning));
  --color-error:           rgb(var(--v-theme-error));
  --color-info:            rgb(var(--v-theme-info));
}

/* safelist classes used dynamically via color="..." prop */
@source inline('bg-primary');
@source inline('text-primary');
@source inline('bg-success');
@source inline('text-success');
@source inline('bg-error');
@source inline('text-error');
```

Disable Vuetify's own theme utility classes to avoid duplicate `bg-*` / `text-*` rules that can't be used with variants:

```ts { resource="vuetify.ts" }
theme: {
  defaultTheme: 'light', // 'system' requires ssr: false in Nuxt
  utilities: false, // skip .bg-primary, .text-error, etc.
},
```

::: warning

Vuetify's original `bg-*` utilities automatically set a contrasting foreground color via `--v-theme-on-*`. Replacing them with TailwindCSS utilities removes this safeguard — you are responsible for choosing legible text colors. See [Limitations](/features/css-utilities/#limitations).

:::

## Using MD3 elevation

The elevation approach in [Configure TailwindCSS](#configure-tailwindcss) maps `elevation-*` to TailwindCSS's generic shadow tokens. If you need Vuetify's exact Material Design 3 shadow values — including the surface overlay tint that shifts with depth — replace those rules with the full MD3 definitions:

<details>
<summary>MD3 elevation utilities (elevation-0 … elevation-5)</summary>

```css { resource="tailwind.css" }
@utility elevation-0 {
  box-shadow: 0px 0px 0px 0px rgba(var(--v-shadow-color), var(--v-shadow-key-opacity, 0.3)), 0px 0px 0px 0px rgba(var(--v-shadow-color), var(--v-shadow-ambient-opacity, 0.15));
  --v-elevation-overlay: color-mix(in srgb, var(--v-elevation-overlay-color) 0%, transparent);
}
@utility elevation-1 {
  box-shadow: 0px 1px 2px 0px rgba(var(--v-shadow-color), var(--v-shadow-key-opacity, 0.3)), 0px 1px 3px 1px rgba(var(--v-shadow-color), var(--v-shadow-ambient-opacity, 0.15));
  --v-elevation-overlay: color-mix(in srgb, var(--v-elevation-overlay-color) 2%, transparent);
}
@utility elevation-2 {
  box-shadow: 0px 1px 2px 0px rgba(var(--v-shadow-color), var(--v-shadow-key-opacity, 0.3)), 0px 2px 6px 2px rgba(var(--v-shadow-color), var(--v-shadow-ambient-opacity, 0.15));
  --v-elevation-overlay: color-mix(in srgb, var(--v-elevation-overlay-color) 4%, transparent);
}
@utility elevation-3 {
  box-shadow: 0px 1px 3px 0px rgba(var(--v-shadow-color), var(--v-shadow-key-opacity, 0.3)), 0px 4px 8px 3px rgba(var(--v-shadow-color), var(--v-shadow-ambient-opacity, 0.15));
  --v-elevation-overlay: color-mix(in srgb, var(--v-elevation-overlay-color) 6%, transparent);
}
@utility elevation-4 {
  box-shadow: 0px 2px 3px 0px rgba(var(--v-shadow-color), var(--v-shadow-key-opacity, 0.3)), 0px 6px 10px 4px rgba(var(--v-shadow-color), var(--v-shadow-ambient-opacity, 0.15));
  --v-elevation-overlay: color-mix(in srgb, var(--v-elevation-overlay-color) 8%, transparent);
}
@utility elevation-5 {
  box-shadow: 0px 4px 4px 0px rgba(var(--v-shadow-color), var(--v-shadow-key-opacity, 0.3)), 0px 8px 12px 6px rgba(var(--v-shadow-color), var(--v-shadow-ambient-opacity, 0.15));
  --v-elevation-overlay: color-mix(in srgb, var(--v-elevation-overlay-color) 10%, transparent);
}
@utility elevation-overlay {
  background-image: linear-gradient(var(--v-elevation-overlay), var(--v-elevation-overlay));
}
```

</details>
