diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d27203950a..23e50613bf2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- _Experimental_: Add `user-valid` and `user-invalid` variants ([#12370](https://github.com/tailwindlabs/tailwindcss/pull/12370)) + ### Fixed - Export `tailwindcss/lib/util/flattenColorPalette.js` for backward compatibility ([#16411](https://github.com/tailwindlabs/tailwindcss/pull/16411)) diff --git a/packages/@tailwindcss-browser/tsup.config.ts b/packages/@tailwindcss-browser/tsup.config.ts index 5a6d78273e65..bb0ddc39cde4 100644 --- a/packages/@tailwindcss-browser/tsup.config.ts +++ b/packages/@tailwindcss-browser/tsup.config.ts @@ -11,5 +11,6 @@ export default defineConfig({ }, define: { 'process.env.NODE_ENV': '"production"', + 'process.env.FEATURES_ENV': '"stable"', }, }) diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap index 62c5f49a4a51..18384759c727 100644 --- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap @@ -8405,6 +8405,8 @@ exports[`getVariants 1`] = ` "required", "valid", "invalid", + "user-valid", + "user-invalid", "in-range", "out-of-range", "read-only", @@ -8474,6 +8476,8 @@ exports[`getVariants 1`] = ` "required", "valid", "invalid", + "user-valid", + "user-invalid", "in-range", "out-of-range", "read-only", @@ -8527,6 +8531,8 @@ exports[`getVariants 1`] = ` "required", "valid", "invalid", + "user-valid", + "user-invalid", "in-range", "out-of-range", "read-only", @@ -8754,6 +8760,20 @@ exports[`getVariants 1`] = ` "selectors": [Function], "values": [], }, + { + "hasDash": true, + "isArbitrary": false, + "name": "user-valid", + "selectors": [Function], + "values": [], + }, + { + "hasDash": true, + "isArbitrary": false, + "name": "user-invalid", + "selectors": [Function], + "values": [], + }, { "hasDash": true, "isArbitrary": false, @@ -8867,6 +8887,8 @@ exports[`getVariants 1`] = ` "required", "valid", "invalid", + "user-valid", + "user-invalid", "in-range", "out-of-range", "read-only", @@ -8920,6 +8942,8 @@ exports[`getVariants 1`] = ` "required", "valid", "invalid", + "user-valid", + "user-invalid", "in-range", "out-of-range", "read-only", diff --git a/packages/tailwindcss/src/feature-flags.ts b/packages/tailwindcss/src/feature-flags.ts index b24550429193..e6db9b7a6261 100644 --- a/packages/tailwindcss/src/feature-flags.ts +++ b/packages/tailwindcss/src/feature-flags.ts @@ -1 +1,3 @@ export const enableRemoveUnusedThemeVariables = false + +export const enableUserValid = process.env.FEATURES_ENV !== 'stable' diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts index be374dbd9196..554be3c4d1b6 100644 --- a/packages/tailwindcss/src/variants.test.ts +++ b/packages/tailwindcss/src/variants.test.ts @@ -325,6 +325,34 @@ test('invalid', async () => { expect(await run(['invalid/foo:flex'])).toEqual('') }) +test('user-valid', async () => { + expect(await run(['user-valid:flex', 'group-user-valid:flex', 'peer-user-valid:flex'])) + .toMatchInlineSnapshot(` + ".group-user-valid\\:flex:is(:where(.group):user-valid *), .peer-user-valid\\:flex:is(:where(.peer):user-valid ~ *) { + display: flex; + } + + .user-valid\\:flex:user-valid { + display: flex; + }" + `) + expect(await run(['user-valid/foo:flex'])).toEqual('') +}) + +test('user-invalid', async () => { + expect(await run(['user-invalid:flex', 'group-user-invalid:flex', 'peer-user-invalid:flex'])) + .toMatchInlineSnapshot(` + ".group-user-invalid\\:flex:is(:where(.group):user-invalid *), .peer-user-invalid\\:flex:is(:where(.peer):user-invalid ~ *) { + display: flex; + } + + .user-invalid\\:flex:user-invalid { + display: flex; + }" + `) + expect(await run(['invalid/foo:flex'])).toEqual('') +}) + test('in-range', async () => { expect(await run(['in-range:flex', 'group-in-range:flex', 'peer-in-range:flex'])) .toMatchInlineSnapshot(` diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index 20f84c867453..ea1de7c8acb6 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -12,6 +12,7 @@ import { type StyleRule, } from './ast' import { type Variant } from './candidate' +import { enableUserValid } from './feature-flags' import type { Theme } from './theme' import { compareBreakpoints } from './utils/compare-breakpoints' import { DefaultMap } from './utils/default-map' @@ -692,6 +693,10 @@ export function createVariants(theme: Theme): Variants { staticVariant('required', ['&:required']) staticVariant('valid', ['&:valid']) staticVariant('invalid', ['&:invalid']) + if (enableUserValid) { + staticVariant('user-valid', ['&:user-valid']) + staticVariant('user-invalid', ['&:user-invalid']) + } staticVariant('in-range', ['&:in-range']) staticVariant('out-of-range', ['&:out-of-range']) staticVariant('read-only', ['&:read-only'])