modernizeArbitraryValuesVariant() — tailwindcss Function Reference
Architecture documentation for the modernizeArbitraryValuesVariant() function in canonicalize-candidates.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD c17298f2_5925_766a_629e_a0acb211713d["modernizeArbitraryValuesVariant()"] 53ff5109_5a35_0396_6060_ad17d2d3ada8["walkVariants()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| 53ff5109_5a35_0396_6060_ad17d2d3ada8 51e8aa8c_3952_5644_8c1c_bfad5401464f["isSingleSelector()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| 51e8aa8c_3952_5644_8c1c_bfad5401464f f79e35a4_ca5c_11c1_36f2_8974eceaf515["replaceObject()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| f79e35a4_ca5c_11c1_36f2_8974eceaf515 ceb1ff41_a641_27dd_6289_7f779710b411["parseVariant()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| ceb1ff41_a641_27dd_6289_7f779710b411 1febc78c_5fc5_7a9e_bbde_e23926ba37fc["printVariant()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| 1febc78c_5fc5_7a9e_bbde_e23926ba37fc e458fb1c_8116_cbd4_432d_0478bd3e6056["isAttributeSelector()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| e458fb1c_8116_cbd4_432d_0478bd3e6056 49592b0a_bbf7_95e1_186d_a8e977ffebd6["selector()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| 49592b0a_bbf7_95e1_186d_a8e977ffebd6 4eec17e9_9a96_168a_2273_b6e64e229816["isPositiveInteger()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| 4eec17e9_9a96_168a_2273_b6e64e229816 0aa64a1c_efd8_a69d_48ed_649b7a86c854["get()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| 0aa64a1c_efd8_a69d_48ed_649b7a86c854 fd9d5dae_4dbc_46b8_74f1_c7c51a381d0a["walk()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| fd9d5dae_4dbc_46b8_74f1_c7c51a381d0a 063cca62_1add_5b64_da79_f2aa9a1e5509["parse()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| 063cca62_1add_5b64_da79_f2aa9a1e5509 e7a34553_0273_6202_4792_07409e33d8f0["toCss()"] c17298f2_5925_766a_629e_a0acb211713d -->|calls| e7a34553_0273_6202_4792_07409e33d8f0 style c17298f2_5925_766a_629e_a0acb211713d 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