Home / Function/ createVariants() — tailwindcss Function Reference

createVariants() — tailwindcss Function Reference

Architecture documentation for the createVariants() function in variants.ts from the tailwindcss codebase.

Entity Profile

Dependency Diagram

graph TD
  7c96535c_85cb_d6bf_efe4_875fba595c4f["createVariants()"]
  cebe77e1_f0f2_aeee_417e_2192f5790344["buildDesignSystem()"]
  cebe77e1_f0f2_aeee_417e_2192f5790344 -->|calls| 7c96535c_85cb_d6bf_efe4_875fba595c4f
  828655d7_fafb_f7a4_ba36_e6b356a78fbc["compoundsForSelectors()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| 828655d7_fafb_f7a4_ba36_e6b356a78fbc
  08f33202_11d1_569a_e8df_de23eb987e2f["rule()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| 08f33202_11d1_569a_e8df_de23eb987e2f
  2a20ea29_c850_cd61_5600_aeebbe3dda66["segment()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| 2a20ea29_c850_cd61_5600_aeebbe3dda66
  a9af385a_fd12_f1d8_7cf0_ccb9b281ca18["atRule()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| a9af385a_fd12_f1d8_7cf0_ccb9b281ca18
  8711d3c6_2537_4267_acab_f8fa7a5ab188["compound()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| 8711d3c6_2537_4267_acab_f8fa7a5ab188
  e9d556bc_f22d_356c_1bd2_27442c34b5c7["walk()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| e9d556bc_f22d_356c_1bd2_27442c34b5c7
  c3b56f1d_0d90_0f17_2f55_85f3419d74bd["styleRule()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| c3b56f1d_0d90_0f17_2f55_85f3419d74bd
  e69fc175_885d_a758_85b9_9613d68e20bf["suggest()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| e69fc175_885d_a758_85b9_9613d68e20bf
  0cd2c95f_7ec0_fa14_e5f7_51185e900451["keys()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| 0cd2c95f_7ec0_fa14_e5f7_51185e900451
  322c45ea_3835_3229_6b47_7ac91365ee73["compoundsWith()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| 322c45ea_3835_3229_6b47_7ac91365ee73
  b9f8f3ed_4296_2840_57dd_a1c25e0e5f52["atRoot()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| b9f8f3ed_4296_2840_57dd_a1c25e0e5f52
  85b46de2_edfa_9371_e2c6_e60f3f5346a2["decl()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| 85b46de2_edfa_9371_e2c6_e60f3f5346a2
  cd658659_fd0a_fcda_c564_b848d3481771["has()"]
  7c96535c_85cb_d6bf_efe4_875fba595c4f -->|calls| cd658659_fd0a_fcda_c564_b848d3481771
  style 7c96535c_85cb_d6bf_efe4_875fba595c4f fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

packages/tailwindcss/src/variants.ts lines 348–1163

export function createVariants(theme: Theme): Variants {
  // In the future we may want to support returning a rule here if some complex
  // variant requires it. For now pure mutation is sufficient and will be the
  // most performant.
  let variants = new Variants()

  /**
   * Register a static variant like `hover`.
   */
  function staticVariant(
    name: string,
    selectors: string[],
    { compounds }: { compounds?: Compounds } = {},
  ) {
    compounds = compounds ?? compoundsForSelectors(selectors)

    variants.static(
      name,
      (r) => {
        r.nodes = selectors.map((selector) => rule(selector, r.nodes))
      },
      { compounds },
    )
  }

  staticVariant('*', [':is(& > *)'], { compounds: Compounds.Never })
  staticVariant('**', [':is(& *)'], { compounds: Compounds.Never })

  function negateConditions(ruleName: string, conditions: string[]) {
    return conditions.map((condition) => {
      condition = condition.trim()

      let parts = segment(condition, ' ')

      // @media not {query}
      // @supports not {query}
      // @container not {query}
      if (parts[0] === 'not') {
        return parts.slice(1).join(' ')
      }

      if (ruleName === '@container') {
        // @container {query}
        if (parts[0][0] === '(') {
          return `not ${condition}`
        }

        // @container {name} not {query}
        else if (parts[1] === 'not') {
          return `${parts[0]} ${parts.slice(2).join(' ')}`
        }

        // @container {name} {query}
        else {
          return `${parts[0]} not ${parts.slice(1).join(' ')}`
        }
      }

      return `not ${condition}`
    })
  }

  let conditionalRules = ['@media', '@supports', '@container']

  function negateAtRule(rule: AtRule) {
    for (let ruleName of conditionalRules) {
      if (ruleName !== rule.name) continue

      let conditions = segment(rule.params, ',')

      // We don't support things like `@media screen, print` because
      // the negation would be `@media not screen and print` and we don't
      // want to deal with that complexity.
      if (conditions.length > 1) return null

      conditions = negateConditions(rule.name, conditions)
      return atRule(rule.name, conditions.join(', '))
    }

    return null
  }

  function negateSelector(selector: string) {
    if (selector.includes('::')) return null

    let selectors = segment(selector, ',').map((sel) => {
      // Replace `&` in target variant with `*`, so variants like `&:hover`
      // become `&:not(*:hover)`. The `*` will often be optimized away.
      sel = sel.replaceAll('&', '*')

      return sel
    })

    return `&:not(${selectors.join(', ')})`
  }

  variants.compound('not', Compounds.StyleRules | Compounds.AtRules, (ruleNode, variant) => {
    if (variant.variant.kind === 'arbitrary' && variant.variant.relative) return null

    if (variant.modifier) return null

    let didApply = false

    walk([ruleNode], (node, ctx) => {
      if (node.kind !== 'rule' && node.kind !== 'at-rule') return WalkAction.Continue
      if (node.nodes.length > 0) return WalkAction.Continue

      // Throw out any candidates with variants using nested style rules
      let atRules: AtRule[] = []
      let styleRules: StyleRule[] = []

      let path = ctx.path()
      path.push(node)

      for (let node of path) {
        if (node.kind === 'at-rule') {
          atRules.push(node)
        } else if (node.kind === 'rule') {
          styleRules.push(node)
        }
      }

      if (atRules.length > 1) return WalkAction.Stop
      if (styleRules.length > 1) return WalkAction.Stop

      let rules: Rule[] = []

      for (let node of styleRules) {
        let selector = negateSelector(node.selector)
        if (!selector) {
          didApply = false
          return WalkAction.Stop
        }

        rules.push(styleRule(selector, []))
      }

      for (let node of atRules) {
        let negatedAtRule = negateAtRule(node)
        if (!negatedAtRule) {
          didApply = false
          return WalkAction.Stop
        }

        rules.push(negatedAtRule)
      }

      Object.assign(ruleNode, styleRule('&', rules))

      // Track that the variant was actually applied
      didApply = true

      return WalkAction.Skip
    })

    // TODO: Tweak group, peer, has to ignore intermediate `&` selectors (maybe?)
    if (ruleNode.kind === 'rule' && ruleNode.selector === '&' && ruleNode.nodes.length === 1) {
      Object.assign(ruleNode, ruleNode.nodes[0])
    }

    // If the node wasn't modified, this variant is not compatible with
    // `not-*` so discard the candidate.
    if (!didApply) return null
  })

  variants.suggest('not', () => {
    return Array.from(variants.keys()).filter((name) => {
      return variants.compoundsWith('not', name)
    })
  })

  variants.compound('group', Compounds.StyleRules, (ruleNode, variant) => {
    if (variant.variant.kind === 'arbitrary' && variant.variant.relative) return null

    // Name the group by appending the modifier to `group` class itself if
    // present.
    let variantSelector = variant.modifier
      ? `:where(.${theme.prefix ? `${theme.prefix}\\:` : ''}group\\/${variant.modifier.value})`
      : `:where(.${theme.prefix ? `${theme.prefix}\\:` : ''}group)`

    let didApply = false

    walk([ruleNode], (node, ctx) => {
      if (node.kind !== 'rule') return WalkAction.Continue

      // Throw out any candidates with variants using nested style rules
      for (let parent of ctx.path()) {
        if (parent.kind !== 'rule') continue

        didApply = false
        return WalkAction.Stop
      }

      // For most variants we rely entirely on CSS nesting to build-up the final
      // selector, but there is no way to use CSS nesting to make `&` refer to
      // just the `.group` class the way we'd need to for these variants, so we
      // need to replace it in the selector ourselves.
      let selector = node.selector.replaceAll('&', variantSelector)

      // When the selector is a selector _list_ we need to wrap it in `:is`
      // to make sure the matching behavior is consistent with the original
      // variant / selector.
      if (segment(selector, ',').length > 1) {
        selector = `:is(${selector})`
      }

      node.selector = `&:is(${selector} *)`

      // Track that the variant was actually applied
      didApply = true
    })

    // If the node wasn't modified, this variant is not compatible with
    // `group-*` so discard the candidate.
    if (!didApply) return null
  })

  variants.suggest('group', () => {
    return Array.from(variants.keys()).filter((name) => {
      return variants.compoundsWith('group', name)
    })
  })

  variants.compound('peer', Compounds.StyleRules, (ruleNode, variant) => {
    if (variant.variant.kind === 'arbitrary' && variant.variant.relative) return null

    // Name the peer by appending the modifier to `peer` class itself if
    // present.
    let variantSelector = variant.modifier
      ? `:where(.${theme.prefix ? `${theme.prefix}\\:` : ''}peer\\/${variant.modifier.value})`
      : `:where(.${theme.prefix ? `${theme.prefix}\\:` : ''}peer)`

    let didApply = false

    walk([ruleNode], (node, ctx) => {
      if (node.kind !== 'rule') return WalkAction.Continue

      // Throw out any candidates with variants using nested style rules
      for (let parent of ctx.path()) {
        if (parent.kind !== 'rule') continue

        didApply = false
        return WalkAction.Stop
      }

      // For most variants we rely entirely on CSS nesting to build-up the final
      // selector, but there is no way to use CSS nesting to make `&` refer to
      // just the `.group` class the way we'd need to for these variants, so we
      // need to replace it in the selector ourselves.
      let selector = node.selector.replaceAll('&', variantSelector)

      // When the selector is a selector _list_ we need to wrap it in `:is`
      // to make sure the matching behavior is consistent with the original
      // variant / selector.
      if (segment(selector, ',').length > 1) {
        selector = `:is(${selector})`
      }

      node.selector = `&:is(${selector} ~ *)`

      // Track that the variant was actually applied
      didApply = true
    })

    // If the node wasn't modified, this variant is not compatible with
    // `peer-*` so discard the candidate.
    if (!didApply) return null
  })

  variants.suggest('peer', () => {
    return Array.from(variants.keys()).filter((name) => {
      return variants.compoundsWith('peer', name)
    })
  })

  staticVariant('first-letter', ['&::first-letter'])
  staticVariant('first-line', ['&::first-line'])

  staticVariant('marker', [
    '& *::marker',
    '&::marker',
    '& *::-webkit-details-marker',
    '&::-webkit-details-marker',
  ])

  staticVariant('selection', ['& *::selection', '&::selection'])
  staticVariant('file', ['&::file-selector-button'])
  staticVariant('placeholder', ['&::placeholder'])
  staticVariant('backdrop', ['&::backdrop'])
  staticVariant('details-content', ['&::details-content'])

  {
    function contentProperties() {
      return atRoot([
        atRule('@property', '--tw-content', [
          decl('syntax', '"*"'),
          decl('initial-value', '""'),
          decl('inherits', 'false'),
        ]),
      ])
    }
    variants.static(
      'before',
      (v) => {
        v.nodes = [
          styleRule('&::before', [
            contentProperties(),
            decl('content', 'var(--tw-content)'),
            ...v.nodes,
          ]),
        ]
      },
      { compounds: Compounds.Never },
    )

    variants.static(
      'after',
      (v) => {
        v.nodes = [
          styleRule('&::after', [
            contentProperties(),
            decl('content', 'var(--tw-content)'),
            ...v.nodes,
          ]),
        ]
      },
      { compounds: Compounds.Never },
    )
  }

  // Positional
  staticVariant('first', ['&:first-child'])
  staticVariant('last', ['&:last-child'])
  staticVariant('only', ['&:only-child'])
  staticVariant('odd', ['&:nth-child(odd)'])
  staticVariant('even', ['&:nth-child(even)'])
  staticVariant('first-of-type', ['&:first-of-type'])
  staticVariant('last-of-type', ['&:last-of-type'])
  staticVariant('only-of-type', ['&:only-of-type'])

  // State
  staticVariant('visited', ['&:visited'])
  staticVariant('target', ['&:target'])
  staticVariant('open', ['&:is([open], :popover-open, :open)'])

  // Forms
  staticVariant('default', ['&:default'])
  staticVariant('checked', ['&:checked'])
  staticVariant('indeterminate', ['&:indeterminate'])
  staticVariant('placeholder-shown', ['&:placeholder-shown'])
  staticVariant('autofill', ['&:autofill'])
  staticVariant('optional', ['&:optional'])
  staticVariant('required', ['&:required'])
  staticVariant('valid', ['&:valid'])
  staticVariant('invalid', ['&:invalid'])
  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'])

  // Content
  staticVariant('empty', ['&:empty'])

  // Interactive
  staticVariant('focus-within', ['&:focus-within'])
  variants.static('hover', (r) => {
    r.nodes = [styleRule('&:hover', [atRule('@media', '(hover: hover)', r.nodes)])]
  })
  staticVariant('focus', ['&:focus'])
  staticVariant('focus-visible', ['&:focus-visible'])
  staticVariant('active', ['&:active'])
  staticVariant('enabled', ['&:enabled'])
  staticVariant('disabled', ['&:disabled'])

  staticVariant('inert', ['&:is([inert], [inert] *)'])

  variants.compound('in', Compounds.StyleRules, (ruleNode, variant) => {
    if (variant.modifier) return null

    let didApply = false

    walk([ruleNode], (node, ctx) => {
      if (node.kind !== 'rule') return WalkAction.Continue

      // Throw out any candidates with variants using nested style rules
      for (let parent of ctx.path()) {
        if (parent.kind !== 'rule') continue

        didApply = false
        return WalkAction.Stop
      }

      // Replace `&` in target variant with `*`, so variants like `&:hover`
      // become `:where(*:hover) &`. The `*` will often be optimized away.
      node.selector = `:where(${node.selector.replaceAll('&', '*')}) &`

      // Track that the variant was actually applied
      didApply = true
    })

    // If the node wasn't modified, this variant is not compatible with
    // `in-*` so discard the candidate.
    if (!didApply) return null
  })

  variants.suggest('in', () => {
    return Array.from(variants.keys()).filter((name) => {
      return variants.compoundsWith('in', name)
    })
  })

  variants.compound('has', Compounds.StyleRules, (ruleNode, variant) => {
    if (variant.modifier) return null

    let didApply = false

    walk([ruleNode], (node, ctx) => {
      if (node.kind !== 'rule') return WalkAction.Continue

      // Throw out any candidates with variants using nested style rules
      for (let parent of ctx.path()) {
        if (parent.kind !== 'rule') continue

        didApply = false
        return WalkAction.Stop
      }

      // Replace `&` in target variant with `*`, so variants like `&:hover`
      // become `&:has(*:hover)`. The `*` will often be optimized away.
      node.selector = `&:has(${node.selector.replaceAll('&', '*')})`

      // Track that the variant was actually applied
      didApply = true
    })

    // If the node wasn't modified, this variant is not compatible with
    // `has-*` so discard the candidate.
    if (!didApply) return null
  })

  variants.suggest('has', () => {
    return Array.from(variants.keys()).filter((name) => {
      return variants.compoundsWith('has', name)
    })
  })

  variants.functional('aria', (ruleNode, variant) => {
    if (!variant.value || variant.modifier) return null

    if (variant.value.kind === 'arbitrary') {
      ruleNode.nodes = [
        styleRule(`&[aria-${quoteAttributeValue(variant.value.value)}]`, ruleNode.nodes),
      ]
    } else {
      ruleNode.nodes = [styleRule(`&[aria-${variant.value.value}="true"]`, ruleNode.nodes)]
    }
  })

  variants.suggest('aria', () => [
    'busy',
    'checked',
    'disabled',
    'expanded',
    'hidden',
    'pressed',
    'readonly',
    'required',
    'selected',
  ])

  variants.functional('data', (ruleNode, variant) => {
    if (!variant.value || variant.modifier) return null

    ruleNode.nodes = [
      styleRule(`&[data-${quoteAttributeValue(variant.value.value)}]`, ruleNode.nodes),
    ]
  })

  variants.functional('nth', (ruleNode, variant) => {
    if (!variant.value || variant.modifier) return null

    // Only numeric bare values are allowed
    if (variant.value.kind === 'named' && !isPositiveInteger(variant.value.value)) return null

    ruleNode.nodes = [styleRule(`&:nth-child(${variant.value.value})`, ruleNode.nodes)]
  })

  variants.functional('nth-last', (ruleNode, variant) => {
    if (!variant.value || variant.modifier) return null

    // Only numeric bare values are allowed
    if (variant.value.kind === 'named' && !isPositiveInteger(variant.value.value)) return null

    ruleNode.nodes = [styleRule(`&:nth-last-child(${variant.value.value})`, ruleNode.nodes)]
  })

  variants.functional('nth-of-type', (ruleNode, variant) => {
    if (!variant.value || variant.modifier) return null

    // Only numeric bare values are allowed
    if (variant.value.kind === 'named' && !isPositiveInteger(variant.value.value)) return null

    ruleNode.nodes = [styleRule(`&:nth-of-type(${variant.value.value})`, ruleNode.nodes)]
  })

  variants.functional('nth-last-of-type', (ruleNode, variant) => {
    if (!variant.value || variant.modifier) return null

    // Only numeric bare values are allowed
    if (variant.value.kind === 'named' && !isPositiveInteger(variant.value.value)) return null

    ruleNode.nodes = [styleRule(`&:nth-last-of-type(${variant.value.value})`, ruleNode.nodes)]
  })

  variants.functional(
    'supports',
    (ruleNode, variant) => {
      if (!variant.value || variant.modifier) return null

      let value = variant.value.value
      if (value === null) return null

      // When the value starts with `not()`, `selector()`, `font-tech()`, or
      // other functions, we can use the value as-is.
      if (/^[\w-]*\s*\(/.test(value)) {
        // Chrome has a bug where `(condition1)or(condition2)` is not valid, but
        // `(condition1) or (condition2)` is supported.
        let query = value.replace(/\b(and|or|not)\b/g, ' $1 ')

        ruleNode.nodes = [atRule('@supports', query, ruleNode.nodes)]
        return
      }

      // When `supports-[display]` is used as a shorthand, we need to make sure
      // that this becomes a valid CSS supports condition.
      //
      // E.g.: `supports-[display]` -> `@supports (display: var(--tw))`
      if (!value.includes(':')) {
        value = `${value}: var(--tw)`
      }

      // When `supports-[display:flex]` is used, we need to make sure that this
      // becomes a valid CSS supports condition by wrapping it in parens.
      //
      // E.g.: `supports-[display:flex]` -> `@supports (display: flex)`
      //
      // We also have to make sure that we wrap the value in parens if the last
      // character is a paren already for situations where we are testing
      // against a CSS custom property.
      //
      // E.g.: `supports-[display]:flex` -> `@supports (display: var(--tw))`
      if (value[0] !== '(' || value[value.length - 1] !== ')') {
        value = `(${value})`
      }

      ruleNode.nodes = [atRule('@supports', value, ruleNode.nodes)]
    },
    { compounds: Compounds.AtRules },
  )

  staticVariant('motion-safe', ['@media (prefers-reduced-motion: no-preference)'])
  staticVariant('motion-reduce', ['@media (prefers-reduced-motion: reduce)'])

  staticVariant('contrast-more', ['@media (prefers-contrast: more)'])
  staticVariant('contrast-less', ['@media (prefers-contrast: less)'])

  {
    // Helper to compare variants by their resolved values, this is used by the
    // responsive variants (`sm`, `md`, ...), `min-*`, `max-*` and container
    // queries (`@`).
    function compareBreakpointVariants(
      a: Variant,
      z: Variant,
      direction: 'asc' | 'desc',
      lookup: { get(v: Variant): string | null },
    ) {
      if (a === z) return 0
      let aValue = lookup.get(a)
      if (aValue === null) return direction === 'asc' ? -1 : 1

      let zValue = lookup.get(z)
      if (zValue === null) return direction === 'asc' ? 1 : -1

      return compareBreakpoints(aValue, zValue, direction)
    }

    // Breakpoints
    {
      let breakpoints = theme.namespace('--breakpoint')
      let resolvedBreakpoints = new DefaultMap((variant: Variant) => {
        switch (variant.kind) {
          case 'static': {
            return theme.resolveValue(variant.root, ['--breakpoint']) ?? null
          }

          case 'functional': {
            if (!variant.value || variant.modifier) return null

            let value: string | null = null

            if (variant.value.kind === 'arbitrary') {
              value = variant.value.value
            } else if (variant.value.kind === 'named') {
              value = theme.resolveValue(variant.value.value, ['--breakpoint'])
            }

            if (!value) return null
            if (value.includes('var(')) return null

            return value
          }
          case 'arbitrary':
          case 'compound':
            return null
        }
      })

      // Max
      variants.group(
        () => {
          variants.functional(
            'max',
            (ruleNode, variant) => {
              if (variant.modifier) return null
              let value = resolvedBreakpoints.get(variant)
              if (value === null) return null

              ruleNode.nodes = [atRule('@media', `(width < ${value})`, ruleNode.nodes)]
            },
            { compounds: Compounds.AtRules },
          )
        },
        (a, z) => compareBreakpointVariants(a, z, 'desc', resolvedBreakpoints),
      )

      variants.suggest(
        'max',
        () => Array.from(breakpoints.keys()).filter((key) => key !== null) as string[],
      )

      // Min
      variants.group(
        () => {
          // Registers breakpoint variants like `sm`, `md`, `lg`, etc.
          for (let [key, value] of theme.namespace('--breakpoint')) {
            if (key === null) continue
            variants.static(
              key,
              (ruleNode) => {
                ruleNode.nodes = [atRule('@media', `(width >= ${value})`, ruleNode.nodes)]
              },
              { compounds: Compounds.AtRules },
            )
          }

          variants.functional(
            'min',
            (ruleNode, variant) => {
              if (variant.modifier) return null
              let value = resolvedBreakpoints.get(variant)
              if (value === null) return null

              ruleNode.nodes = [atRule('@media', `(width >= ${value})`, ruleNode.nodes)]
            },
            { compounds: Compounds.AtRules },
          )
        },
        (a, z) => compareBreakpointVariants(a, z, 'asc', resolvedBreakpoints),
      )

      variants.suggest(
        'min',
        () => Array.from(breakpoints.keys()).filter((key) => key !== null) as string[],
      )
    }

    {
      let widths = theme.namespace('--container')

      // Container queries
      let resolvedWidths = new DefaultMap((variant: Variant) => {
        switch (variant.kind) {
          case 'functional': {
            if (variant.value === null) return null

            let value: string | null = null

            if (variant.value.kind === 'arbitrary') {
              value = variant.value.value
            } else if (variant.value.kind === 'named') {
              value = theme.resolveValue(variant.value.value, ['--container'])
            }

            if (!value) return null
            if (value.includes('var(')) return null

            return value
          }
          case 'static':
          case 'arbitrary':
          case 'compound':
            return null
        }
      })

      variants.group(
        () => {
          variants.functional(
            '@max',
            (ruleNode, variant) => {
              let value = resolvedWidths.get(variant)
              if (value === null) return null

              ruleNode.nodes = [
                atRule(
                  '@container',
                  variant.modifier
                    ? `${variant.modifier.value} (width < ${value})`
                    : `(width < ${value})`,
                  ruleNode.nodes,
                ),
              ]
            },
            { compounds: Compounds.AtRules },
          )
        },
        (a, z) => compareBreakpointVariants(a, z, 'desc', resolvedWidths),
      )

      variants.suggest(
        '@max',
        () => Array.from(widths.keys()).filter((key) => key !== null) as string[],
      )

      variants.group(
        () => {
          variants.functional(
            '@',
            (ruleNode, variant) => {
              let value = resolvedWidths.get(variant)
              if (value === null) return null

              ruleNode.nodes = [
                atRule(
                  '@container',
                  variant.modifier
                    ? `${variant.modifier.value} (width >= ${value})`
                    : `(width >= ${value})`,
                  ruleNode.nodes,
                ),
              ]
            },
            { compounds: Compounds.AtRules },
          )
          variants.functional(
            '@min',
            (ruleNode, variant) => {
              let value = resolvedWidths.get(variant)
              if (value === null) return null

              ruleNode.nodes = [
                atRule(
                  '@container',
                  variant.modifier
                    ? `${variant.modifier.value} (width >= ${value})`
                    : `(width >= ${value})`,
                  ruleNode.nodes,
                ),
              ]
            },
            { compounds: Compounds.AtRules },
          )
        },
        (a, z) => compareBreakpointVariants(a, z, 'asc', resolvedWidths),
      )

      variants.suggest(
        '@min',
        () => Array.from(widths.keys()).filter((key) => key !== null) as string[],
      )

      variants.suggest(
        '@',
        () => Array.from(widths.keys()).filter((key) => key !== null) as string[],
      )
    }
  }

  staticVariant('portrait', ['@media (orientation: portrait)'])
  staticVariant('landscape', ['@media (orientation: landscape)'])

  staticVariant('ltr', ['&:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *)'])
  staticVariant('rtl', ['&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)'])

  staticVariant('dark', ['@media (prefers-color-scheme: dark)'])

  staticVariant('starting', ['@starting-style'])

  staticVariant('print', ['@media print'])

  staticVariant('forced-colors', ['@media (forced-colors: active)'])

  staticVariant('inverted-colors', ['@media (inverted-colors: inverted)'])

  staticVariant('pointer-none', ['@media (pointer: none)'])
  staticVariant('pointer-coarse', ['@media (pointer: coarse)'])
  staticVariant('pointer-fine', ['@media (pointer: fine)'])
  staticVariant('any-pointer-none', ['@media (any-pointer: none)'])
  staticVariant('any-pointer-coarse', ['@media (any-pointer: coarse)'])
  staticVariant('any-pointer-fine', ['@media (any-pointer: fine)'])

  staticVariant('noscript', ['@media (scripting: none)'])

  return variants
}

Subdomains

Frequently Asked Questions

What does createVariants() do?
createVariants() is a function in the tailwindcss codebase.
What does createVariants() call?
createVariants() calls 21 function(s): atRoot, atRule, compareBreakpoints, compound, compoundsForSelectors, compoundsWith, decl, functional, and 13 more.
What calls createVariants()?
createVariants() is called by 1 function(s): buildDesignSystem.

Analyze Your Own Codebase

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

Try Supermodel Free