Home / Function/ parseVariant() — tailwindcss Function Reference

parseVariant() — tailwindcss Function Reference

Architecture documentation for the parseVariant() function in candidate.ts from the tailwindcss codebase.

Entity Profile

Dependency Diagram

graph TD
  ca76ae68_c9c0_d977_a6d8_8ba86685bf25["parseVariant()"]
  c0ed9081_e732_ecfa_0427_6bc0211bcee4["migrateArbitraryVariants()"]
  c0ed9081_e732_ecfa_0427_6bc0211bcee4 -->|calls| ca76ae68_c9c0_d977_a6d8_8ba86685bf25
  486b36da_67cd_fb5a_cf7f_c5aca1c480be["migrateModernizeArbitraryValues()"]
  486b36da_67cd_fb5a_cf7f_c5aca1c480be -->|calls| ca76ae68_c9c0_d977_a6d8_8ba86685bf25
  53cf41fe_5903_d247_3bb3_38414ba7d631["parseCandidate()"]
  53cf41fe_5903_d247_3bb3_38414ba7d631 -->|calls| ca76ae68_c9c0_d977_a6d8_8ba86685bf25
  d6d6389c_e8ac_a603_fb73_c284bcbea150["arbitraryVariants()"]
  d6d6389c_e8ac_a603_fb73_c284bcbea150 -->|calls| ca76ae68_c9c0_d977_a6d8_8ba86685bf25
  24d8d1d0_89ac_76a1_956a_704ad43fcba6["modernizeArbitraryValuesVariant()"]
  24d8d1d0_89ac_76a1_956a_704ad43fcba6 -->|calls| ca76ae68_c9c0_d977_a6d8_8ba86685bf25
  cebe77e1_f0f2_aeee_417e_2192f5790344["buildDesignSystem()"]
  cebe77e1_f0f2_aeee_417e_2192f5790344 -->|calls| ca76ae68_c9c0_d977_a6d8_8ba86685bf25
  716eb081_2da3_764c_1bd4_89abf211e018["substituteAtVariant()"]
  716eb081_2da3_764c_1bd4_89abf211e018 -->|calls| ca76ae68_c9c0_d977_a6d8_8ba86685bf25
  20489d8d_4ac1_6581_7ef8_8b43d79a6212["decodeArbitraryValue()"]
  ca76ae68_c9c0_d977_a6d8_8ba86685bf25 -->|calls| 20489d8d_4ac1_6581_7ef8_8b43d79a6212
  d744638c_ed47_aa8c_13df_82eac08886a4["isValidArbitrary()"]
  ca76ae68_c9c0_d977_a6d8_8ba86685bf25 -->|calls| d744638c_ed47_aa8c_13df_82eac08886a4
  2a20ea29_c850_cd61_5600_aeebbe3dda66["segment()"]
  ca76ae68_c9c0_d977_a6d8_8ba86685bf25 -->|calls| 2a20ea29_c850_cd61_5600_aeebbe3dda66
  12046d5a_fe45_6a31_232f_f0f54b577fe6["findRoots()"]
  ca76ae68_c9c0_d977_a6d8_8ba86685bf25 -->|calls| 12046d5a_fe45_6a31_232f_f0f54b577fe6
  db928a82_c6a9_f0a7_efee_5436571565b0["parseModifier()"]
  ca76ae68_c9c0_d977_a6d8_8ba86685bf25 -->|calls| db928a82_c6a9_f0a7_efee_5436571565b0
  style ca76ae68_c9c0_d977_a6d8_8ba86685bf25 fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

packages/tailwindcss/src/candidate.ts lines 661–849

export function parseVariant(variant: string, designSystem: DesignSystem): Variant | null {
  // Arbitrary variants
  if (variant[0] === '[' && variant[variant.length - 1] === ']') {
    /**
     * TODO: Breaking change
     *
     * @deprecated Arbitrary variants containing at-rules with other selectors
     * are deprecated. Use stacked variants instead.
     *
     * Before:
     *  - `[@media(width>=123px){&:hover}]:`
     *
     * After:
     *  - `[@media(width>=123px)]:[&:hover]:`
     *  - `[@media(width>=123px)]:hover:`
     */
    if (variant[1] === '@' && variant.includes('&')) return null

    let selector = decodeArbitraryValue(variant.slice(1, -1))

    // Values can't contain `;` or `}` characters at the top-level.
    if (!isValidArbitrary(selector)) return null

    // Empty arbitrary values are invalid. E.g.: `[]:`
    //                                            ^^
    if (selector.length === 0 || selector.trim().length === 0) return null

    let relative = selector[0] === '>' || selector[0] === '+' || selector[0] === '~'

    // Ensure `&` is always present by wrapping the selector in `&:is(…)`,
    // unless it's a relative selector like `> img`.
    //
    // E.g.:
    //
    // - `[p]:flex`
    if (!relative && selector[0] !== '@' && !selector.includes('&')) {
      selector = `&:is(${selector})`
    }

    return {
      kind: 'arbitrary',
      selector,
      relative,
    }
  }

  // Static, functional and compound variants
  {
    // group-hover/group-name
    // ^^^^^^^^^^^            -> Variant without modifier
    //             ^^^^^^^^^^ -> Modifier
    let [variantWithoutModifier, modifier = null, additionalModifier] = segment(variant, '/')

    // If there's more than one modifier, the variant is invalid.
    //
    // E.g.:
    //
    // - `group-hover/foo/bar`
    if (additionalModifier) return null

    let roots = findRoots(variantWithoutModifier, (root) => {
      return designSystem.variants.has(root)
    })

    for (let [root, value] of roots) {
      switch (designSystem.variants.kind(root)) {
        case 'static': {
          // Static variants do not have a value
          if (value !== null) return null

          // Static variants do not have a modifier
          if (modifier !== null) return null

          return {
            kind: 'static',
            root,
          }
        }

        case 'functional': {
          let parsedModifier = modifier === null ? null : parseModifier(modifier)
          // Empty arbitrary values are invalid. E.g.: `@max-md/[]:` or `@max-md/():`
          //                                                    ^^               ^^
          if (modifier !== null && parsedModifier === null) return null

          if (value === null) {
            return {
              kind: 'functional',
              root,
              modifier: parsedModifier,
              value: null,
            }
          }

          if (value[value.length - 1] === ']') {
            // Discard values like `foo-[#bar]`
            if (value[0] !== '[') continue

            let arbitraryValue = decodeArbitraryValue(value.slice(1, -1))

            // Values can't contain `;` or `}` characters at the top-level.
            if (!isValidArbitrary(arbitraryValue)) return null

            // Empty arbitrary values are invalid. E.g.: `data-[]:`
            //                                                 ^^
            if (arbitraryValue.length === 0 || arbitraryValue.trim().length === 0) return null

            return {
              kind: 'functional',
              root,
              modifier: parsedModifier,
              value: {
                kind: 'arbitrary',
                value: arbitraryValue,
              },
            }
          }

          if (value[value.length - 1] === ')') {
            // Discard values like `foo-(--bar)`
            if (value[0] !== '(') continue

            let arbitraryValue = decodeArbitraryValue(value.slice(1, -1))

            // Values can't contain `;` or `}` characters at the top-level.
            if (!isValidArbitrary(arbitraryValue)) return null

            // Empty arbitrary values are invalid. E.g.: `data-():`
            //                                                 ^^
            if (arbitraryValue.length === 0 || arbitraryValue.trim().length === 0) return null

            // Arbitrary values must start with `--` since it represents a CSS variable.
            if (arbitraryValue[0] !== '-' || arbitraryValue[1] !== '-') return null

            return {
              kind: 'functional',
              root,
              modifier: parsedModifier,
              value: {
                kind: 'arbitrary',
                value: `var(${arbitraryValue})`,
              },
            }
          }

          if (!IS_VALID_NAMED_VALUE.test(value)) continue

          return {
            kind: 'functional',
            root,
            modifier: parsedModifier,
            value: { kind: 'named', value },
          }
        }

        case 'compound': {
          if (value === null) return null

          // Forward the modifier of the compound variants to its subVariant.
          // This allows for `not-group-hover/name:flex` to work.
          if (modifier && (root === 'not' || root === 'has' || root === 'in')) {
            value = `${value}/${modifier}`
            modifier = null
          }

          let subVariant = designSystem.parseVariant(value)
          if (subVariant === null) return null

          // These two variants must be compatible when compounded
          if (!designSystem.variants.compoundsWith(root, subVariant)) return null

          let parsedModifier = modifier === null ? null : parseModifier(modifier)
          // Empty arbitrary values are invalid. E.g.: `group-focus/[]:` or `group-focus/():`
          //                                                        ^^                   ^^
          if (modifier !== null && parsedModifier === null) return null

          return {
            kind: 'compound',
            root,
            modifier: parsedModifier,
            variant: subVariant,
          }
        }
      }
    }
  }

  return null
}

Subdomains

Frequently Asked Questions

What does parseVariant() do?
parseVariant() is a function in the tailwindcss codebase.
What does parseVariant() call?
parseVariant() calls 5 function(s): decodeArbitraryValue, findRoots, isValidArbitrary, parseModifier, segment.
What calls parseVariant()?
parseVariant() is called by 7 function(s): arbitraryVariants, buildDesignSystem, migrateArbitraryVariants, migrateModernizeArbitraryValues, modernizeArbitraryValuesVariant, parseCandidate, substituteAtVariant.

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free