diff --git a/integrations/vite/index.test.ts b/integrations/vite/index.test.ts
index 28c218b81227..ab22fa20cc5f 100644
--- a/integrations/vite/index.test.ts
+++ b/integrations/vite/index.test.ts
@@ -1216,6 +1216,199 @@ test(
},
)
+test(
+ 'optimize option: advanced Lightning CSS settings',
+ {
+ fs: {
+ 'package.json': txt`
+ {
+ "type": "module",
+ "dependencies": {
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "lightningcss": "^1",
+ "vite": "^7"
+ }
+ }
+ `,
+ 'vite.config.ts': ts`
+ import { Features } from 'lightningcss'
+ import tailwindcss from '@tailwindcss/vite'
+ import { defineConfig } from 'vite'
+
+ export default defineConfig({
+ build: { cssMinify: false },
+ plugins: [
+ tailwindcss({
+ optimize: {
+ include: Features.Nesting,
+ targets: { chrome: 999 << 16 },
+ drafts: { customMedia: false },
+ nonStandard: { deepSelectorCombinator: true },
+ },
+ }),
+ ],
+ })
+ `,
+ 'index.html': html`
+
+
+
+
+ Hello, world!
+
+ `,
+ 'src/index.css': css`
+ @reference 'tailwindcss/theme';
+ @import 'tailwindcss/utilities';
+
+ @custom-media --viewport-medium (width >= 700px);
+
+ @media (--viewport-medium) {
+ .custom {
+ display: flex;
+ }
+ }
+ `,
+ },
+ },
+ async ({ exec, expect, fs }) => {
+ await exec('pnpm vite build')
+
+ let files = await fs.glob('dist/**/*.css')
+ expect(files).toHaveLength(1)
+ let [filename] = files[0]
+
+ let content = await fs.read(filename)
+ expect(content).toContain('.hover\\:flex:hover {')
+ expect(content).toContain('@media (width >= 700px) {')
+ expect(content).toContain('@custom-media --viewport-medium (width >= 700px);')
+ expect(content).toContain('@media (--viewport-medium) {')
+ },
+)
+
+test(
+ 'optimize option: advanced Lightning CSS exclude',
+ {
+ fs: {
+ 'package.json': txt`
+ {
+ "type": "module",
+ "dependencies": {
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "lightningcss": "^1",
+ "vite": "^7"
+ }
+ }
+ `,
+ 'vite.config.ts': ts`
+ import { Features } from 'lightningcss'
+ import tailwindcss from '@tailwindcss/vite'
+ import { defineConfig } from 'vite'
+
+ export default defineConfig({
+ build: { cssMinify: false },
+ plugins: [
+ tailwindcss({
+ optimize: {
+ minify: false,
+ include: Features.MediaQueries,
+ exclude: Features.Nesting,
+ },
+ }),
+ ],
+ })
+ `,
+ 'index.html': html`
+
+
+
+
+ Hello, world!
+
+ `,
+ 'src/index.css': css`
+ @reference 'tailwindcss/theme';
+ @import 'tailwindcss/utilities';
+ `,
+ },
+ },
+ async ({ exec, expect, fs }) => {
+ await exec('pnpm vite build')
+
+ let files = await fs.glob('dist/**/*.css')
+ expect(files).toHaveLength(1)
+ let [filename] = files[0]
+
+ let content = await fs.read(filename)
+ expect(content).toContain('.hover\\:flex {')
+ expect(content).toContain('&:hover {')
+ },
+)
+
+test(
+ 'polyfills option: disabled',
+ {
+ fs: {
+ 'package.json': txt`
+ {
+ "type": "module",
+ "dependencies": {
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "vite": "^7"
+ }
+ }
+ `,
+ 'vite.config.ts': ts`
+ import tailwindcss from '@tailwindcss/vite'
+ import { Polyfills } from 'tailwindcss'
+ import { defineConfig } from 'vite'
+
+ export default defineConfig({
+ build: { cssMinify: false },
+ plugins: [tailwindcss({ optimize: false, polyfills: Polyfills.None })],
+ })
+ `,
+ 'index.html': html`
+
+
+
+
+ Hello, world!
+
+ `,
+ 'src/index.css': css`
+ @import 'tailwindcss/utilities';
+
+ @property --no-inherit-value {
+ syntax: '*';
+ inherits: false;
+ initial-value: red;
+ }
+ `,
+ },
+ },
+ async ({ exec, expect, fs }) => {
+ await exec('pnpm vite build')
+
+ let files = await fs.glob('dist/**/*.css')
+ expect(files).toHaveLength(1)
+ let [filename] = files[0]
+
+ let content = await fs.read(filename)
+ expect(content).toContain('@property --no-inherit-value')
+ expect(content).not.toContain('@layer properties')
+ },
+)
+
test(
`the plugin works when using the environment API`,
{
diff --git a/packages/@tailwindcss-node/src/optimize.ts b/packages/@tailwindcss-node/src/optimize.ts
index 6e60f8b873f6..2102a110f11d 100644
--- a/packages/@tailwindcss-node/src/optimize.ts
+++ b/packages/@tailwindcss-node/src/optimize.ts
@@ -1,5 +1,5 @@
import remapping from '@jridgewell/remapping'
-import { Features, transform } from 'lightningcss'
+import { Features, transform, type Drafts, type NonStandard, type Targets } from 'lightningcss'
import MagicString from 'magic-string'
export interface OptimizeOptions {
@@ -13,6 +13,31 @@ export interface OptimizeOptions {
*/
minify?: boolean
+ /**
+ * The browser targets for the generated code.
+ */
+ targets?: Targets
+
+ /**
+ * Features that should always be compiled, even when supported by targets.
+ */
+ include?: number
+
+ /**
+ * Features that should never be compiled, even when unsupported by targets.
+ */
+ exclude?: number
+
+ /**
+ * Whether to enable parsing various draft syntax.
+ */
+ drafts?: Drafts
+
+ /**
+ * Whether to enable various non-standard syntax.
+ */
+ nonStandard?: NonStandard
+
/**
* The output source map before optimization
*
@@ -28,7 +53,16 @@ export interface TransformResult {
export function optimize(
input: string,
- { file = 'input.css', minify = false, map }: OptimizeOptions = {},
+ {
+ file = 'input.css',
+ minify = false,
+ map,
+ drafts,
+ nonStandard,
+ include,
+ exclude,
+ targets,
+ }: OptimizeOptions = {},
): TransformResult {
function optimize(code: Buffer | Uint8Array, map: string | undefined) {
return transform({
@@ -37,15 +71,11 @@ export function optimize(
minify,
sourceMap: typeof map !== 'undefined',
inputSourceMap: map,
- drafts: {
- customMedia: true,
- },
- nonStandard: {
- deepSelectorCombinator: true,
- },
- include: Features.Nesting | Features.MediaQueries,
- exclude: Features.LogicalProperties | Features.DirSelector | Features.LightDark,
- targets: {
+ drafts: { customMedia: true, ...drafts },
+ nonStandard: { deepSelectorCombinator: true, ...nonStandard },
+ include: include ?? Features.Nesting | Features.MediaQueries,
+ exclude: exclude ?? Features.LogicalProperties | Features.DirSelector | Features.LightDark,
+ targets: targets ?? {
safari: (16 << 16) | (4 << 8),
ios_saf: (16 << 16) | (4 << 8),
firefox: 128 << 16,
diff --git a/packages/@tailwindcss-vite/README.md b/packages/@tailwindcss-vite/README.md
index 1a5d25c73652..3cf161966b26 100644
--- a/packages/@tailwindcss-vite/README.md
+++ b/packages/@tailwindcss-vite/README.md
@@ -74,3 +74,24 @@ export default defineConfig({
],
})
```
+
+Additional Lightning CSS options can be configured through the `optimize` object, for example `drafts`, `nonStandard`, `include`, `exclude`, and `targets`.
+
+## Controlling Tailwind polyfills
+
+By default, Tailwind emits all supported CSS polyfills. You can customize this behavior using the `polyfills` option:
+
+```js
+import tailwindcss from '@tailwindcss/vite'
+import { defineConfig } from 'vite'
+import { Polyfills } from 'tailwindcss'
+
+export default defineConfig({
+ plugins: [
+ tailwindcss({
+ // Disable all Tailwind polyfills
+ polyfills: Polyfills.None,
+ }),
+ ],
+})
+```
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index ac3e1c33c9af..1c9e9e0b5941 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -5,6 +5,7 @@ import {
Instrumentation,
normalizePath,
optimize,
+ Polyfills,
toSourceMap,
} from '@tailwindcss/node'
import { clearRequireCache } from '@tailwindcss/node/require-cache'
@@ -27,10 +28,17 @@ const COMMON_JS_PROXY_RE = /\?commonjs-proxy/
const INLINE_STYLE_ID_RE = /[?&]index=\d+\.css$/
export type PluginOptions = {
+ /**
+ * Control CSS polyfills emitted by Tailwind.
+ *
+ * Defaults to `Polyfills.All`.
+ */
+ polyfills?: Polyfills
+
/**
* Optimize and minify the output CSS.
*/
- optimize?: boolean | { minify?: boolean }
+ optimize?: boolean | Omit[1]>, 'file' | 'map'>
}
export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
@@ -123,6 +131,7 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
// Currently, Vite only supports CSS source maps in development and they
// are off by default. Check to see if we need them or not.
config?.css.devSourcemap ?? false,
+ opts.polyfills ?? Polyfills.All,
customCssResolver,
customJsResolver,
)
@@ -153,8 +162,8 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
minify = shouldOptimize && config.build.cssMinify !== false
// But again, the user can override that choice explicitly
- if (typeof opts.optimize === 'object') {
- minify = opts.optimize.minify !== false
+ if (typeof opts.optimize === 'object' && opts.optimize.minify !== undefined) {
+ minify = opts.optimize.minify
}
},
},
@@ -310,6 +319,7 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
if (shouldOptimize) {
DEBUG && I.start('[@tailwindcss/vite] Optimize CSS')
result = optimize(result.code, {
+ ...(typeof opts.optimize === 'object' ? opts.optimize : {}),
minify,
map: result.map,
})
@@ -389,6 +399,7 @@ class Root {
private base: string,
private enableSourceMaps: boolean,
+ private polyfills: Polyfills,
private customCssResolver: (id: string, base: string) => Promise,
private customJsResolver: (id: string, base: string) => Promise,
) {}
@@ -438,12 +449,17 @@ class Root {
this.addBuildDependency(idToPath(inputPath))
+ // CSS Modules cannot safely receive the `@property` fallback polyfill
+ // because it emits global `*` rules, which Vite treats as non-pure.
DEBUG && I.start('Setup compiler')
let addBuildDependenciesPromises: Promise[] = []
this.compiler = await compile(content, {
from: this.enableSourceMaps ? this.id : undefined,
base: inputBase,
shouldRewriteUrls: true,
+ polyfills: inputPath.endsWith('.module.css')
+ ? this.polyfills & ~Polyfills.AtProperty
+ : this.polyfills,
onDependency: (path) => {
addWatchFile(path)
addBuildDependenciesPromises.push(this.addBuildDependency(path))