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
}
Domain
Subdomains
Calls
Called By
Source
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