Home / Function/ createCssUtility() — tailwindcss Function Reference

createCssUtility() — tailwindcss Function Reference

Architecture documentation for the createCssUtility() function in utilities.ts from the tailwindcss codebase.

Entity Profile

Dependency Diagram

graph TD
  5c9381d6_815c_d899_eaab_849d755be47e["createCssUtility()"]
  26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c["parseCss()"]
  26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 5c9381d6_815c_d899_eaab_849d755be47e
  a5f79231_4cd8_b5f8_d867_2482aada99fc["isValidFunctionalUtilityName()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| a5f79231_4cd8_b5f8_d867_2482aada99fc
  e9d556bc_f22d_356c_1bd2_27442c34b5c7["walk()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| e9d556bc_f22d_356c_1bd2_27442c34b5c7
  0638028e_f2f7_8e77_2f19_1bd2ce4b2d6a["parse()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| 0638028e_f2f7_8e77_2f19_1bd2ce4b2d6a
  2a20ea29_c850_cd61_5600_aeebbe3dda66["segment()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| 2a20ea29_c850_cd61_5600_aeebbe3dda66
  92969a3b_d253_e151_139a_8e2f44014af0["entries()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| 92969a3b_d253_e151_139a_8e2f44014af0
  3ba19013_498f_3c9b_5c44_0eb24efc4394["add()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| 3ba19013_498f_3c9b_5c44_0eb24efc4394
  c0082127_5fa8_53a3_42a8_ebd3055f8a2f["functional()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| c0082127_5fa8_53a3_42a8_ebd3055f8a2f
  51b06849_01ef_68aa_d189_1f0d4376d897["cloneAstNode()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| 51b06849_01ef_68aa_d189_1f0d4376d897
  84f8ccf2_2af5_fa7c_53f2_954b21183738["resolveValueFunction()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| 84f8ccf2_2af5_fa7c_53f2_954b21183738
  e4c5db63_934c_465b_0a8a_6fe2fc7ed83f["suggest()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| e4c5db63_934c_465b_0a8a_6fe2fc7ed83f
  559275a8_927e_3002_3298_4cbb685cc92a["isPositiveInteger()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| 559275a8_927e_3002_3298_4cbb685cc92a
  3d6e204b_1a08_95c4_b252_89c58ff8ac22["keysInNamespaces()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| 3d6e204b_1a08_95c4_b252_89c58ff8ac22
  12881e61_4009_1000_ab3f_8c343a68c8b8["isValidStaticUtilityName()"]
  5c9381d6_815c_d899_eaab_849d755be47e -->|calls| 12881e61_4009_1000_ab3f_8c343a68c8b8
  style 5c9381d6_815c_d899_eaab_849d755be47e fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

packages/tailwindcss/src/utilities.ts lines 5941–6301

export function createCssUtility(node: AtRule) {
  let name = node.params

  // Functional utilities. E.g.: `tab-size-*`
  if (isValidFunctionalUtilityName(name)) {
    // API:
    //
    // - `--value('literal')`         resolves a literal named value
    // - `--value(number)`            resolves a bare value of type number
    // - `--value([number])`          resolves an arbitrary value of type number
    // - `--value(--color)`           resolves a theme value in the `color` namespace
    // - `--value(number, [number])`  resolves a bare value of type number or an
    //                                arbitrary value of type number in order.
    //
    // Rules:
    //
    // - If `--value(…)` does not resolve to a valid value, the declaration is
    //   removed.
    // - If a `--value(ratio)` resolves, the `--modifier(…)` cannot be used.
    // - If a candidate looks like `foo-2/3`, then the `--value(ratio)` should
    //   be used OR the `--value(…)` and `--modifier(…)` must be used. But not
    //   both.
    // - All parts of the candidate must resolve, otherwise it's not a valid
    //   utility. E.g.:`
    //   ```
    //   @utility foo-* {
    //     test: --value(number)
    //   }
    //   ```
    //   If you then use `foo-1/2`, this is invalid, because the modifier is not used.

    return (designSystem: DesignSystem) => {
      let storage = {
        '--value': {
          usedSpacingInteger: false,
          usedSpacingNumber: false,
          themeKeys: new Set<`--${string}`>(),
          literals: new Set<string>(),
        },
        '--modifier': {
          usedSpacingInteger: false,
          usedSpacingNumber: false,
          themeKeys: new Set<`--${string}`>(),
          literals: new Set<string>(),
        },
      }

      // Pre-process the AST to make it easier to work with.
      //
      // - Normalize theme values used in `--value(…)` and `--modifier(…)`
      //   functions.
      // - Track information for suggestions
      walk(node.nodes, (child) => {
        if (child.kind !== 'declaration') return
        if (!child.value) return
        if (!child.value.includes('--value(') && !child.value.includes('--modifier(')) return

        let declarationValueAst = ValueParser.parse(child.value)

        // Required manipulations:
        //
        // - `--value(--spacing)`                 -> `--value(--spacing-*)`
        // - `--value(--spacing- *)`              -> `--value(--spacing-*)`
        // - `--value(--text- * --line-height)`   -> `--value(--text-*--line-height)`
        // - `--value(--text --line-height)`      -> `--value(--text-*--line-height)`
        // - `--value(--text-\\* --line-height)`  -> `--value(--text-*--line-height)`
        // - `--value([ *])`                      -> `--value([*])`
        //
        // Once Prettier / Biome handle these better (e.g.: not crashing without
        // `\\*` or not inserting whitespace) then most of these can go away.
        walk(declarationValueAst, (fn) => {
          if (fn.kind !== 'function') return

          // Track usage of `--spacing(…)`
          if (
            fn.value === '--spacing' &&
            // Quick bail check if we already know that `--value` and `--modifier` are
            // using the full `--spacing` theme scale.
            !(storage['--modifier'].usedSpacingNumber && storage['--value'].usedSpacingNumber)
          ) {
            walk(fn.nodes, (node) => {
              if (node.kind !== 'function') return
              if (node.value !== '--value' && node.value !== '--modifier') return
              const key = node.value

              for (let arg of node.nodes) {
                if (arg.kind !== 'word') continue

                if (arg.value === 'integer') {
                  storage[key].usedSpacingInteger ||= true
                } else if (arg.value === 'number') {
                  storage[key].usedSpacingNumber ||= true

                  // Once both `--value` and `--modifier` are using the full
                  // `number` spacing scale, then there's no need to continue
                  if (
                    storage['--modifier'].usedSpacingNumber &&
                    storage['--value'].usedSpacingNumber
                  ) {
                    return WalkAction.Stop
                  }
                }
              }
            })
            return WalkAction.Continue
          }

          if (fn.value !== '--value' && fn.value !== '--modifier') return

          let args = segment(ValueParser.toCss(fn.nodes), ',')
          for (let [idx, arg] of args.entries()) {
            // Transform escaped `\\*` -> `*`
            arg = arg.replace(/\\\*/g, '*')

            // Ensure `--value(--foo --bar)` becomes `--value(--foo-*--bar)`
            arg = arg.replace(/--(.*?)\s--(.*?)/g, '--$1-*--$2')

            // Remove whitespace, e.g.: `--value([ *])` -> `--value([*])`
            arg = arg.replace(/\s+/g, '')

            // Ensure multiple `-*` becomes a single `-*`
            arg = arg.replace(/(-\*){2,}/g, '-*')

            // Ensure trailing `-*` exists if `-*` isn't present yet
            if (arg[0] === '-' && arg[1] === '-' && !arg.includes('-*')) {
              arg += '-*'
            }

            args[idx] = arg
          }
          fn.nodes = ValueParser.parse(args.join(','))

          for (let node of fn.nodes) {
            // Track literal values
            if (
              node.kind === 'word' &&
              (node.value[0] === '"' || node.value[0] === "'") &&
              node.value[0] === node.value[node.value.length - 1]
            ) {
              let value = node.value.slice(1, -1)
              storage[fn.value].literals.add(value)
            }

            // Track theme keys
            else if (node.kind === 'word' && node.value[0] === '-' && node.value[1] === '-') {
              let value = node.value.replace(/-\*.*$/g, '') as `--${string}`
              storage[fn.value].themeKeys.add(value)
            }

            // Validate bare value data types
            else if (
              node.kind === 'word' &&
              !(node.value[0] === '[' && node.value[node.value.length - 1] === ']') && // Ignore arbitrary values
              !BARE_VALUE_DATA_TYPES.includes(node.value)
            ) {
              console.warn(
                `Unsupported bare value data type: "${node.value}".\nOnly valid data types are: ${BARE_VALUE_DATA_TYPES.map((x) => `"${x}"`).join(', ')}.\n`,
              )
              // TODO: Once we properly track the location of the node, we can
              //       clean this up in a better way.
              let dataType = node.value
              let copy = structuredClone(fn)
              let sentinelValue = '¶'
              walk(copy.nodes, (node) => {
                if (node.kind === 'word' && node.value === dataType) {
                  return WalkAction.ReplaceSkip({ kind: 'word', value: sentinelValue } as const)
                }
              })
              let underline = '^'.repeat(ValueParser.toCss([node]).length)
              let offset = ValueParser.toCss([copy]).indexOf(sentinelValue)
              let output = [
                '```css',
                ValueParser.toCss([fn]),
                ' '.repeat(offset) + underline,
                '```',
              ].join('\n')
              console.warn(output)
            }
          }
        })

        child.value = ValueParser.toCss(declarationValueAst)
      })

      designSystem.utilities.functional(name.slice(0, -2), (candidate) => {
        let atRule = cloneAstNode(node)

        let value = candidate.value
        let modifier = candidate.modifier

        // A value is required for functional utilities, if you want to accept
        // just `tab-size`, you'd have to use a static utility.
        if (value === null) return

        // Whether `--value(…)` was used
        let usedValueFn = false

        // Whether any of the declarations successfully resolved a `--value(…)`.
        // E.g:
        // ```css
        // @utility tab-size-* {
        //   tab-size: --value(integer);
        //   tab-size: --value(--tab-size);
        //   tab-size: --value([integer]);
        // }
        // ```
        // Any of these `tab-size` declarations have to resolve to a valid in
        // order to make the utility valid.
        let resolvedValueFn = false

        // Whether `--modifier(…)` was used
        let usedModifierFn = false

        // Whether any of the declarations successfully resolved a `--modifier(…)`
        let resolvedModifierFn = false

        // A map of all declarations we replaced and their parent rules. We
        // might need to remove some later on. E.g.: remove declarations that
        // used `--value(number)` when `--value(ratio)` was resolved.
        let resolvedDeclarations = new Map<Declaration, AtRule | Rule>()

        // Whether `--value(ratio)` was resolved
        let resolvedRatioValue = false

        walk<AstNode>([atRule], (node, ctx) => {
          let parent = ctx.parent
          if (parent?.kind !== 'rule' && parent?.kind !== 'at-rule') return
          if (node.kind !== 'declaration') return
          if (!node.value) return

          let shouldRemoveDeclaration = false

          let valueAst = ValueParser.parse(node.value)
          walk(valueAst, (valueNode) => {
            if (valueNode.kind !== 'function') return

            // Value function, e.g.: `--value(integer)`
            if (valueNode.value === '--value') {
              usedValueFn = true

              let resolved = resolveValueFunction(value, valueNode, designSystem)
              if (resolved) {
                resolvedValueFn = true
                if (resolved.ratio) {
                  resolvedRatioValue = true
                } else {
                  resolvedDeclarations.set(node, parent)
                }
                return WalkAction.ReplaceSkip(resolved.nodes)
              }

              // Drop the declaration in case we couldn't resolve the value
              usedValueFn ||= false
              shouldRemoveDeclaration = true
              return WalkAction.Stop
            }

            // Modifier function, e.g.: `--modifier(integer)`
            else if (valueNode.value === '--modifier') {
              // If there is no modifier present in the candidate, then the
              // declaration can be removed.
              if (modifier === null) {
                shouldRemoveDeclaration = true
                return WalkAction.Stop
              }

              usedModifierFn = true

              let replacement = resolveValueFunction(modifier, valueNode, designSystem)
              if (replacement) {
                resolvedModifierFn = true
                return WalkAction.ReplaceSkip(replacement.nodes)
              }

              // Drop the declaration in case we couldn't resolve the value
              usedModifierFn ||= false
              shouldRemoveDeclaration = true
              return WalkAction.Stop
            }
          })

          if (shouldRemoveDeclaration) {
            return WalkAction.ReplaceSkip([])
          }

          node.value = ValueParser.toCss(valueAst)
        })

        // Used `--value(…)` but nothing resolved
        if (usedValueFn && !resolvedValueFn) return null

        // Used `--modifier(…)` but nothing resolved
        if (usedModifierFn && !resolvedModifierFn) return null

        // Resolved `--value(ratio)` and `--modifier(…)`, which is invalid
        if (resolvedRatioValue && resolvedModifierFn) return null

        // When a candidate has a modifier, then the `--modifier(…)` must
        // resolve correctly or the `--value(ratio)` must resolve correctly.
        if (modifier && !resolvedRatioValue && !resolvedModifierFn) return null

        // Resolved `--value(ratio)`, so all other declarations that didn't use
        // `--value(ratio)` should be removed. E.g.: `--value(number)` would
        // otherwise resolve for `foo-1/2`.
        if (resolvedRatioValue) {
          for (let [declaration, parent] of resolvedDeclarations) {
            let idx = parent.nodes.indexOf(declaration)
            if (idx !== -1) parent.nodes.splice(idx, 1)
          }
        }

        return atRule.nodes
      })

      designSystem.utilities.suggest(name.slice(0, -2), () => {
        let values: string[] = []
        let modifiers: string[] = []

        for (let [target, { literals, usedSpacingNumber, usedSpacingInteger, themeKeys }] of [
          [values, storage['--value']],
          [modifiers, storage['--modifier']],
        ] as const) {
          // Suggest literal values. E.g.: `--value('literal')`
          for (let value of literals) {
            target.push(value)
          }

          // Suggest `--spacing(…)` values. E.g.: `--spacing(--value(integer))`
          if (usedSpacingNumber) {
            target.push(...DEFAULT_SPACING_SUGGESTIONS)
          } else if (usedSpacingInteger) {
            for (let value of DEFAULT_SPACING_SUGGESTIONS) {
              if (isPositiveInteger(value)) {
                target.push(value)
              }
            }
          }

          // Suggest theme values. E.g.: `--value(--color-*)`
          for (let value of designSystem.theme.keysInNamespaces(themeKeys)) {
            target.push(
              value.replace(LEGACY_NUMERIC_KEY, (_, a, b) => {
                return `${a}.${b}`
              }),
            )
          }
        }

        return [{ values, modifiers }] satisfies SuggestionGroup[]
      })
    }
  }

  if (isValidStaticUtilityName(name)) {
    return (designSystem: DesignSystem) => {
      designSystem.utilities.static(name, () => node.nodes.map(cloneAstNode))
    }
  }

  return null
}

Subdomains

Called By

Frequently Asked Questions

What does createCssUtility() do?
createCssUtility() is a function in the tailwindcss codebase.
What does createCssUtility() call?
createCssUtility() calls 14 function(s): add, cloneAstNode, entries, functional, isPositiveInteger, isValidFunctionalUtilityName, isValidStaticUtilityName, keysInNamespaces, and 6 more.
What calls createCssUtility()?
createCssUtility() is called by 1 function(s): parseCss.

Analyze Your Own Codebase

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

Try Supermodel Free