diff --git a/CHANGELOG.md b/CHANGELOG.md
index 39e89832bf42..bafb6c74434e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add support for Vite 8 in `@tailwindcss/vite` ([#19790](https://github.com/tailwindlabs/tailwindcss/pull/19790))
- Improve canonicalization for bare values exceeding default spacing scale suggestions (e.g. `w-1234 h-1234` → `size-1234`) ([#19809](https://github.com/tailwindlabs/tailwindcss/pull/19809))
- Fix canonicalization resulting in empty list (e.g. `w-5 h-5 size-5` → `''` instead of `size-5`) ([#19812](https://github.com/tailwindlabs/tailwindcss/pull/19812))
+- Resolve tsconfig paths to allow for `@import '@/path/to/file';` when using `@tailwindcss/vite` ([#19803](https://github.com/tailwindlabs/tailwindcss/pull/19803))
## [4.2.1] - 2026-02-23
diff --git a/integrations/vite/resolvers.test.ts b/integrations/vite/resolvers.test.ts
index f8e389a7e696..79ce57553555 100644
--- a/integrations/vite/resolvers.test.ts
+++ b/integrations/vite/resolvers.test.ts
@@ -1,5 +1,168 @@
import { describe } from 'vitest'
-import { candidate, css, fetchStyles, html, js, retryAssertion, test, ts, txt } from '../utils'
+import {
+ candidate,
+ css,
+ fetchStyles,
+ html,
+ js,
+ json,
+ retryAssertion,
+ test,
+ ts,
+ txt,
+} from '../utils'
+
+test(
+ 'resolves tsconfig paths in production build',
+ {
+ fs: {
+ 'package.json': json`
+ {
+ "type": "module",
+ "dependencies": {
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "vite": "^8"
+ }
+ }
+ `,
+ 'tsconfig.json': json`
+ {
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+ }
+ `,
+ 'vite.config.ts': ts`
+ import tailwindcss from '@tailwindcss/vite'
+ import { defineConfig } from 'vite'
+
+ export default defineConfig({
+ build: { cssMinify: false },
+ plugins: [tailwindcss()],
+ resolve: {
+ tsconfigPaths: true,
+ },
+ })
+ `,
+ 'index.html': html`
+
+
+
+
+ Hello, world!
+
+ `,
+ 'src/index.css': css`
+ @import '@/styles/base.css';
+ @plugin '@/plugin.js';
+ `,
+ 'src/styles/base.css': css`
+ @reference 'tailwindcss/theme';
+ @import 'tailwindcss/utilities';
+ `,
+ 'src/plugin.js': js`
+ export default function ({ addUtilities }) {
+ addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } })
+ }
+ `,
+ },
+ },
+ async ({ fs, exec, expect }) => {
+ await exec('pnpm vite build')
+
+ let files = await fs.glob('dist/**/*.css')
+ expect(files).toHaveLength(1)
+ let [filename] = files[0]
+
+ await fs.expectFileToContain(filename, [candidate`underline`, candidate`custom-underline`])
+ },
+)
+
+test(
+ 'resolves tsconfig paths in dev mode',
+ {
+ fs: {
+ 'package.json': json`
+ {
+ "type": "module",
+ "dependencies": {
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "vite": "^8"
+ }
+ }
+ `,
+ 'tsconfig.json': json`
+ {
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+ }
+ `,
+ 'vite.config.ts': ts`
+ import tailwindcss from '@tailwindcss/vite'
+ import { defineConfig } from 'vite'
+
+ export default defineConfig({
+ build: { cssMinify: false },
+ plugins: [tailwindcss()],
+ resolve: {
+ tsconfigPaths: true,
+ },
+ })
+ `,
+ 'index.html': html`
+
+
+
+
+ Hello, world!
+
+ `,
+ 'src/index.css': css`
+ @import '@/styles/base.css';
+ @plugin '@/plugin.js';
+ `,
+ 'src/styles/base.css': css`
+ @reference 'tailwindcss/theme';
+ @import 'tailwindcss/utilities';
+ `,
+ 'src/plugin.js': js`
+ export default function ({ addUtilities }) {
+ addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } })
+ }
+ `,
+ },
+ },
+ async ({ spawn, expect }) => {
+ let process = await spawn('pnpm vite dev')
+ await process.onStdout((m) => m.includes('ready in'))
+
+ let url = ''
+ await process.onStdout((m) => {
+ let match = /Local:\s*(http.*)\//.exec(m)
+ if (match) url = match[1]
+ return Boolean(url)
+ })
+
+ await retryAssertion(async () => {
+ let styles = await fetchStyles(url, '/index.html')
+ expect(styles).toContain(candidate`underline`)
+ expect(styles).toContain(candidate`custom-underline`)
+ })
+ },
+)
describe.each(['postcss', 'lightningcss'])('%s', (transformer) => {
test(
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index cd0249680331..ac3e1c33c9af 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -62,8 +62,12 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
let jsResolver = config!.createResolver(config!.resolve)
- customCssResolver = (id: string, base: string) => cssResolver(id, base, true, isSSR)
- customJsResolver = (id: string, base: string) => jsResolver(id, base, true, isSSR)
+ customCssResolver = async (id: string, base: string) => {
+ let resolved = await cssResolver(id, base, false, isSSR)
+ if (resolved && !resolved.endsWith('.css')) return undefined
+ return resolved
+ }
+ customJsResolver = (id: string, base: string) => jsResolver(id, base, false, isSSR)
} else {
type ResolveIdFn = (
environment: Environment,
@@ -105,8 +109,12 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
let jsResolver = createBackCompatIdResolver(env.config, env.config.resolve)
- customCssResolver = (id: string, base: string) => cssResolver(env, id, base, true)
- customJsResolver = (id: string, base: string) => jsResolver(env, id, base, true)
+ customCssResolver = async (id: string, base: string) => {
+ let resolved = await cssResolver(env, id, base, false)
+ if (resolved && !resolved.endsWith('.css')) return undefined
+ return resolved
+ }
+ customJsResolver = (id: string, base: string) => jsResolver(env, id, base, false)
}
return new Root(