Introduce :user-valid & :user-invalid variants (#12370)

See
- https://developer.mozilla.org/en-US/docs/Web/CSS/:user-valid
- https://developer.mozilla.org/en-US/docs/Web/CSS/:user-invalid

Just landed in Chrome, Firefox & Safari:
- https://caniuse.com/mdn-css_selectors_user-valid
- https://caniuse.com/mdn-css_selectors_user-invalid

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This commit is contained in:
Martijn Cuppens 2025-02-12 17:17:54 +01:00 • committed by GitHub
parent c80c0b5128
commit 7326f64c41
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 64 additions and 0 deletions

View file

@ -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))

View file

@ -11,5 +11,6 @@ export default defineConfig({
},
define: {
'process.env.NODE_ENV': '"production"',
'process.env.FEATURES_ENV': '"stable"',
},
})

View file

@ -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",

View file

@ -1 +1,3 @@
export const enableRemoveUnusedThemeVariables = false
export const enableUserValid = process.env.FEATURES_ENV !== 'stable'

View file

@ -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(`

View file

@ -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'])