Home / Function/ modernizeArbitraryValuesVariant() — tailwindcss Function Reference

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
}

Subdomains

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