modernizeArbitraryValuesVariant() — tailwindcss Function Reference
Architecture documentation for the modernizeArbitraryValuesVariant() function in canonicalize-candidates.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD 24d8d1d0_89ac_76a1_956a_704ad43fcba6["modernizeArbitraryValuesVariant()"] 5fbe9f30_4678_6100_0870_5216b48ccf87["walkVariants()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| 5fbe9f30_4678_6100_0870_5216b48ccf87 6f9af745_4a1a_4092_d77a_3472d1310429["isSingleSelector()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| 6f9af745_4a1a_4092_d77a_3472d1310429 2d70e77d_ce8f_238d_2b32_a8b4190f2628["replaceObject()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| 2d70e77d_ce8f_238d_2b32_a8b4190f2628 ca76ae68_c9c0_d977_a6d8_8ba86685bf25["parseVariant()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| ca76ae68_c9c0_d977_a6d8_8ba86685bf25 49c38785_1012_962e_d353_b5b1d6858132["printVariant()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| 49c38785_1012_962e_d353_b5b1d6858132 1ffedaf1_fead_58ff_19e2_f42d5600d960["isAttributeSelector()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| 1ffedaf1_fead_58ff_19e2_f42d5600d960 63943b72_e55f_b9bc_fe20_d9dc11bf39ec["selector()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| 63943b72_e55f_b9bc_fe20_d9dc11bf39ec 559275a8_927e_3002_3298_4cbb685cc92a["isPositiveInteger()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| 559275a8_927e_3002_3298_4cbb685cc92a 4cd99e59_ac1e_2a1f_0946_33cc1afd2532["get()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| 4cd99e59_ac1e_2a1f_0946_33cc1afd2532 e9d556bc_f22d_356c_1bd2_27442c34b5c7["walk()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| e9d556bc_f22d_356c_1bd2_27442c34b5c7 cb368927_d6ec_d016_7fb3_2ea287d31108["parse()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| cb368927_d6ec_d016_7fb3_2ea287d31108 af90c185_29a2_6c4c_ef06_b18f00f7655c["toCss()"] 24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| af90c185_29a2_6c4c_ef06_b18f00f7655c style 24d8d1d0_89ac_76a1_956a_704ad43fcba6 fill:#6366f1,stroke:#818cf8,color:#fff
Relationship Graph
Source Code
packages/tailwindcss/src/canonicalize-candidates.ts lines 1540–1911
function modernizeArbitraryValuesVariant(
variant: Variant,
options: InternalCanonicalizeOptions,
): Variant | Variant[] {
let result = [variant]
let designSystem = options.designSystem
let signatures = designSystem.storage[VARIANT_SIGNATURE_KEY]
let iterator = walkVariants(variant)
for (let [variant, parent] of iterator) {
// Forward modifier from the root to the compound variant
if (
variant.kind === 'compound' &&
(variant.root === 'has' || variant.root === 'not' || variant.root === 'in')
) {
if (variant.modifier !== null) {
if ('modifier' in variant.variant) {
variant.variant.modifier = variant.modifier
variant.modifier = null
}
}
}
// Expecting an arbitrary variant
if (variant.kind === 'arbitrary') {
// Expecting a non-relative arbitrary variant
if (variant.relative) continue
let ast = SelectorParser.parse(variant.selector.trim())
// Expecting a single selector node
if (!isSingleSelector(ast)) continue
// `[&>*]` can be replaced with `*`
if (
// Only top-level, so `has-[&>*]` is not supported
parent === null &&
// [&_>_*]:flex
// ^ ^ ^
ast.length === 3 &&
ast[0].kind === 'selector' &&
ast[0].value === '&' &&
ast[1].kind === 'combinator' &&
ast[1].value.trim() === '>' &&
ast[2].kind === 'selector' &&
ast[2].value === '*'
) {
replaceObject(variant, designSystem.parseVariant('*'))
continue
}
// `[&_*]` can be replaced with `**`
if (
// Only top-level, so `has-[&_*]` is not supported
parent === null &&
// [&_*]:flex
// ^ ^
ast.length === 3 &&
ast[0].kind === 'selector' &&
ast[0].value === '&' &&
ast[1].kind === 'combinator' &&
ast[1].value.trim() === '' && // space, but trimmed because there could be multiple spaces
ast[2].kind === 'selector' &&
ast[2].value === '*'
) {
replaceObject(variant, designSystem.parseVariant('**'))
continue
}
// `in-*` variant. If the selector ends with ` &`, we can convert it to an
// `in-*` variant.
//
// E.g.: `[[data-visible]_&]` => `in-data-visible`
if (
// Only top-level, so `in-[&_[data-visible]]` is not supported
parent === null &&
// [[data-visible]___&]:flex
// ^^^^^^^^^^^^^^ ^ ^
ast.length === 3 &&
ast[1].kind === 'combinator' &&
ast[1].value.trim() === '' && // Space, but trimmed because there could be multiple spaces
ast[2].kind === 'selector' &&
ast[2].value === '&'
) {
ast.pop() // Remove the nesting node
ast.pop() // Remove the combinator
// When handling a compound like `in-[[data-visible]]`, we will first
// handle `[[data-visible]]`, then the parent `in-*` part. This means
// that we can convert `[[data-visible]_&]` to `in-[[data-visible]]`.
//
// Later this gets converted to `in-data-visible`.
replaceObject(variant, designSystem.parseVariant(`in-[${SelectorParser.toCss(ast)}]`))
continue
}
// Hoist `not` modifier for `@media` or `@supports` variants
//
// E.g.: `[@media_not_(scripting:none)]:` -> `not-[@media_(scripting:none)]:`
if (
// Only top-level, so something like `in-[@media(scripting:none)]`
// (which is not valid anyway) is not supported
parent === null &&
// [@media_not(scripting:none)]:flex
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
ast[0].kind === 'selector' &&
(ast[0].value === '@media' || ast[0].value === '@supports')
) {
let targetSignature = signatures.get(designSystem.printVariant(variant))
let parsed = ValueParser.parse(SelectorParser.toCss(ast))
let containsNot = false
walk(parsed, (node) => {
if (node.kind === 'word' && node.value === 'not') {
containsNot = true
return WalkAction.Replace([])
}
})
// Remove unnecessary whitespace
parsed = ValueParser.parse(ValueParser.toCss(parsed))
walk(parsed, (node) => {
if (node.kind === 'separator' && node.value !== ' ' && node.value.trim() === '') {
// node.value contains at least 2 spaces. Normalize it to a single
// space.
node.value = ' '
}
})
if (containsNot) {
let hoistedNot = designSystem.parseVariant(`not-[${ValueParser.toCss(parsed)}]`)
if (hoistedNot === null) continue
let hoistedNotSignature = signatures.get(designSystem.printVariant(hoistedNot))
if (targetSignature === hoistedNotSignature) {
replaceObject(variant, hoistedNot)
continue
}
}
}
let prefixedVariant: Variant | null = null
// Handling a child combinator. E.g.: `[&>[data-visible]]` => `*:data-visible`
if (
// Only top-level, so `has-[&>[data-visible]]` is not supported
parent === null &&
// [&_>_[data-visible]]:flex
// ^ ^ ^^^^^^^^^^^^^^
ast.length === 3 &&
ast[0].kind === 'selector' &&
ast[0].value.trim() === '&' &&
ast[1].kind === 'combinator' &&
ast[1].value.trim() === '>' &&
ast[2].kind === 'selector' &&
(isAttributeSelector(ast[2]) || ast[2].value[0] === ':')
) {
ast = [ast[2]]
prefixedVariant = designSystem.parseVariant('*')
}
// Handling a grand child combinator. E.g.: `[&_[data-visible]]` => `**:data-visible`
if (
// Only top-level, so `has-[&_[data-visible]]` is not supported
parent === null &&
// [&_[data-visible]]:flex
// ^ ^^^^^^^^^^^^^^
ast.length === 3 &&
ast[0].kind === 'selector' &&
ast[0].value.trim() === '&' &&
ast[1].kind === 'combinator' &&
ast[1].value.trim() === '' && // space, but trimmed because there could be multiple spaces
ast[2].kind === 'selector' &&
(isAttributeSelector(ast[2]) || ast[2].value[0] === ':')
) {
ast = [ast[2]]
prefixedVariant = designSystem.parseVariant('**')
}
// Filter out `&`. E.g.: `&[data-foo]` => `[data-foo]`
let selectorNodes = ast.filter(
(node) => !(node.kind === 'selector' && node.value.trim() === '&'),
)
// Expecting a single selector (normal selector or attribute selector)
if (selectorNodes.length !== 1) continue
let target = selectorNodes[0]
if (target.kind === 'function' && target.value === ':is') {
// Expecting a single selector node
if (
!isSingleSelector(target.nodes) ||
// [foo][bar] is considered a single selector but has multiple nodes
target.nodes.length !== 1
) {
continue
}
// Expecting a single attribute selector
if (!isAttributeSelector(target.nodes[0])) continue
// Unwrap the selector from inside `&:is(…)`
target = target.nodes[0]
}
// Expecting a pseudo selector (or function)
if (
(target.kind === 'function' && target.value[0] === ':') ||
(target.kind === 'selector' && target.value[0] === ':')
) {
let targetNode = target
let compoundNot = false
if (targetNode.kind === 'function' && targetNode.value === ':not') {
compoundNot = true
if (targetNode.nodes.length !== 1) continue
if (targetNode.nodes[0].kind !== 'selector' && targetNode.nodes[0].kind !== 'function') {
continue
}
if (targetNode.nodes[0].value[0] !== ':') continue
targetNode = targetNode.nodes[0]
}
let newVariant = ((value) => {
if (
value === ':nth-child' &&
targetNode.kind === 'function' &&
targetNode.nodes.length === 1 &&
targetNode.nodes[0].kind === 'value' &&
targetNode.nodes[0].value === 'odd'
) {
if (compoundNot) {
compoundNot = false
return 'even'
}
return 'odd'
}
if (
value === ':nth-child' &&
targetNode.kind === 'function' &&
targetNode.nodes.length === 1 &&
targetNode.nodes[0].kind === 'value' &&
targetNode.nodes[0].value === 'even'
) {
if (compoundNot) {
compoundNot = false
return 'odd'
}
return 'even'
}
for (let [selector, variantName] of [
[':nth-child', 'nth'],
[':nth-last-child', 'nth-last'],
[':nth-of-type', 'nth-of-type'],
[':nth-last-of-type', 'nth-of-last-type'],
]) {
if (
value === selector &&
targetNode.kind === 'function' &&
targetNode.nodes.length === 1
) {
if (
targetNode.nodes.length === 1 &&
targetNode.nodes[0].kind === 'value' &&
isPositiveInteger(targetNode.nodes[0].value)
) {
return `${variantName}-${targetNode.nodes[0].value}`
}
return `${variantName}-[${SelectorParser.toCss(targetNode.nodes)}]`
}
}
// Hoist `not` modifier
if (compoundNot) {
let targetSignature = signatures.get(designSystem.printVariant(variant))
let replacementSignature = signatures.get(`not-[${value}]`)
if (targetSignature === replacementSignature) {
return `[&${value}]`
}
}
return null
})(targetNode.value)
if (newVariant === null) {
if (prefixedVariant) {
replaceObject(variant, {
kind: 'arbitrary',
selector: target.value,
relative: false,
} satisfies Variant)
return [prefixedVariant, variant]
}
continue
}
// Add `not-` prefix
if (compoundNot) newVariant = `not-${newVariant}`
let parsed = designSystem.parseVariant(newVariant)
if (parsed === null) continue
// Update original variant
replaceObject(variant, parsed)
}
// Expecting an attribute selector
else if (isAttributeSelector(target)) {
let attributeSelector = AttributeSelectorParser.parse(target.value)
if (attributeSelector === null) continue // Invalid attribute selector
// Migrate `data-*`
if (attributeSelector.attribute.startsWith('data-')) {
let name = attributeSelector.attribute.slice(5) // Remove `data-`
replaceObject(variant, {
kind: 'functional',
root: 'data',
modifier: null,
value:
attributeSelector.value === null
? { kind: 'named', value: name }
: {
kind: 'arbitrary',
value: `${name}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`,
},
} satisfies Variant)
}
// Migrate `aria-*`
else if (attributeSelector.attribute.startsWith('aria-')) {
let name = attributeSelector.attribute.slice(5) // Remove `aria-`
replaceObject(variant, {
kind: 'functional',
root: 'aria',
modifier: null,
value:
attributeSelector.value === null
? { kind: 'arbitrary', value: name } // aria-[foo]
: attributeSelector.operator === '=' &&
attributeSelector.value === 'true' &&
attributeSelector.sensitivity === null
? { kind: 'named', value: name } // aria-[foo="true"] or aria-[foo='true'] or aria-[foo=true]
: {
kind: 'arbitrary',
value: `${attributeSelector.attribute}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`,
}, // aria-[foo~="true"], aria-[foo|="true"], …
} satisfies Variant)
}
// Arbitrary attributes
else {
replaceObject(variant, {
kind: 'arbitrary',
selector: target.value,
relative: false,
} satisfies Variant)
}
}
if (prefixedVariant) {
return [prefixedVariant, variant]
}
}
}
return result
}
Domain
Subdomains
Calls
Source
Frequently Asked Questions
What does modernizeArbitraryValuesVariant() do?
modernizeArbitraryValuesVariant() is a function in the tailwindcss codebase.
What does modernizeArbitraryValuesVariant() call?
modernizeArbitraryValuesVariant() calls 12 function(s): get, isAttributeSelector, isPositiveInteger, isSingleSelector, parse, parseVariant, printVariant, replaceObject, and 4 more.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free