buildPluginApi() — tailwindcss Function Reference
Architecture documentation for the buildPluginApi() function in plugin-api.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD ad196438_55f7_af7b_1604_1d75c1c27d8e["buildPluginApi()"] 0f8ac574_990e_8595_a6ed_11422b8a8ec4["upgradeToFullPluginSupport()"] 0f8ac574_990e_8595_a6ed_11422b8a8ec4 -->|calls| ad196438_55f7_af7b_1604_1d75c1c27d8e a073a4cd_758b_23aa_b3d4_216815e4dd3d["objectToAst()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| a073a4cd_758b_23aa_b3d4_216815e4dd3d 4bf7802a_6d99_1399_1f81_b8d1aa3ab685["substituteFunctions()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 4bf7802a_6d99_1399_1f81_b8d1aa3ab685 a9af385a_fd12_f1d8_7cf0_ccb9b281ca18["atRule()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| a9af385a_fd12_f1d8_7cf0_ccb9b281ca18 e9d556bc_f22d_356c_1bd2_27442c34b5c7["walk()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| e9d556bc_f22d_356c_1bd2_27442c34b5c7 23f7e85c_49fd_cf23_507a_d8d770053d92["entries()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 23f7e85c_49fd_cf23_507a_d8d770053d92 3ec7100d_c372_c98a_2ba0_9ec5e70f401d["parseVariantValue()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 3ec7100d_c372_c98a_2ba0_9ec5e70f401d 828655d7_fafb_f7a4_ba36_e6b356a78fbc["compoundsForSelectors()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 828655d7_fafb_f7a4_ba36_e6b356a78fbc 43c6ae59_76f1_6f53_dfa2_f3151fd259d1["fromAst()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 43c6ae59_76f1_6f53_dfa2_f3151fd259d1 3ea38218_2a7c_f912_d458_de38bb3579b6["group()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 3ea38218_2a7c_f912_d458_de38bb3579b6 2a20ea29_c850_cd61_5600_aeebbe3dda66["segment()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 2a20ea29_c850_cd61_5600_aeebbe3dda66 08f33202_11d1_569a_e8df_de23eb987e2f["rule()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 08f33202_11d1_569a_e8df_de23eb987e2f 9508f2e7_ca66_c4bb_0665_b6dc278d127a["replaceNestedClassNameReferences()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 9508f2e7_ca66_c4bb_0665_b6dc278d127a 5f3acb43_b93f_4293_caaa_25ba26d38178["substituteAtApply()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 5f3acb43_b93f_4293_caaa_25ba26d38178 style ad196438_55f7_af7b_1604_1d75c1c27d8e fill:#6366f1,stroke:#818cf8,color:#fff
Relationship Graph
Source Code
packages/tailwindcss/src/compat/plugin-api.ts lines 93–542
export function buildPluginApi({
designSystem,
ast,
resolvedConfig,
featuresRef,
referenceMode,
src,
}: {
designSystem: DesignSystem
ast: AstNode[]
resolvedConfig: ResolvedConfig
featuresRef: { current: Features }
referenceMode: boolean
src: SourceLocation | undefined
}): PluginAPI {
let api: PluginAPI = {
addBase(css) {
if (referenceMode) return
let baseNodes = objectToAst(css)
featuresRef.current |= substituteFunctions(baseNodes, designSystem)
let rule = atRule('@layer', 'base', baseNodes)
walk([rule], (node) => {
node.src = src
})
ast.push(rule)
},
addVariant(name, variant) {
if (!IS_VALID_VARIANT_NAME.test(name)) {
throw new Error(
`\`addVariant('${name}')\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.`,
)
}
// Ignore variants emitting v3 `:merge(…)` rules. In v4, the `group-*` and `peer-*` variants
// compound automatically.
if (typeof variant === 'string') {
if (variant.includes(':merge(')) return
} else if (Array.isArray(variant)) {
if (variant.some((v) => v.includes(':merge('))) return
} else if (typeof variant === 'object') {
function keyIncludes(object: Record<string, any>, search: string): boolean {
return Object.entries(object).some(
([key, value]) =>
key.includes(search) || (typeof value === 'object' && keyIncludes(value, search)),
)
}
if (keyIncludes(variant, ':merge(')) return
}
// Single selector or multiple parallel selectors
if (typeof variant === 'string' || Array.isArray(variant)) {
designSystem.variants.static(
name,
(r) => {
r.nodes = parseVariantValue(variant, r.nodes)
},
{
compounds: compoundsForSelectors(typeof variant === 'string' ? [variant] : variant),
},
)
}
// CSS-in-JS object
else if (typeof variant === 'object') {
designSystem.variants.fromAst(name, objectToAst(variant), designSystem)
}
},
matchVariant(name, fn, options) {
function resolveVariantValue<T extends Parameters<typeof fn>[0]>(
value: T,
modifier: CandidateModifier | null,
nodes: AstNode[],
): AstNode[] {
let resolved = fn(value, { modifier: modifier?.value ?? null })
return parseVariantValue(resolved, nodes)
}
try {
// Sample variant value and ignore variants emitting v3 `:merge` rules. In
// v4, the `group-*` and `peer-*` variants compound automatically.
let sample = fn('a', { modifier: null })
if (typeof sample === 'string' && sample.includes(':merge(')) {
return
} else if (Array.isArray(sample) && sample.some((r) => r.includes(':merge('))) {
return
}
} catch {}
let defaultOptionKeys = Object.keys(options?.values ?? {})
designSystem.variants.group(
() => {
designSystem.variants.functional(name, (ruleNodes, variant) => {
if (!variant.value) {
if (options?.values && 'DEFAULT' in options.values) {
ruleNodes.nodes = resolveVariantValue(
options.values.DEFAULT,
variant.modifier,
ruleNodes.nodes,
)
return
}
return null
}
if (variant.value.kind === 'arbitrary') {
ruleNodes.nodes = resolveVariantValue(
variant.value.value,
variant.modifier,
ruleNodes.nodes,
)
} else if (variant.value.kind === 'named' && options?.values) {
let defaultValue = options.values[variant.value.value]
if (typeof defaultValue !== 'string') {
return null
}
ruleNodes.nodes = resolveVariantValue(defaultValue, variant.modifier, ruleNodes.nodes)
} else {
return null
}
})
},
(a, z) => {
// Since we only define a functional variant in the group, the `kind`
// has to be `functional`.
if (a.kind !== 'functional' || z.kind !== 'functional') {
return 0
}
let aValueKey = a.value ? a.value.value : 'DEFAULT'
let zValueKey = z.value ? z.value.value : 'DEFAULT'
let aValue = options?.values?.[aValueKey] ?? aValueKey
let zValue = options?.values?.[zValueKey] ?? zValueKey
if (options && typeof options.sort === 'function') {
return options.sort(
{ value: aValue, modifier: a.modifier?.value ?? null },
{ value: zValue, modifier: z.modifier?.value ?? null },
)
}
let aOrder = defaultOptionKeys.indexOf(aValueKey)
let zOrder = defaultOptionKeys.indexOf(zValueKey)
// Sort arbitrary values after configured values
aOrder = aOrder === -1 ? defaultOptionKeys.length : aOrder
zOrder = zOrder === -1 ? defaultOptionKeys.length : zOrder
if (aOrder !== zOrder) return aOrder - zOrder
// SAFETY: The values don't need to be checked for equality as they
// are guaranteed to be unique since we sort a list of de-duped
// variants and different (valid) variants cannot produce the same AST.
return aValue < zValue ? -1 : 1
},
)
designSystem.variants.suggest(name, () =>
Object.keys(options?.values ?? {}).filter((v) => v !== 'DEFAULT'),
)
},
addUtilities(utilities) {
utilities = Array.isArray(utilities) ? utilities : [utilities]
let entries = utilities.flatMap((u) => Object.entries(u))
// Split multi-selector utilities into individual utilities
entries = entries.flatMap(([name, css]) =>
segment(name, ',').map((selector) => [selector.trim(), css] as [string, CssInJs]),
)
// Merge entries for the same class
let utils = new DefaultMap<string, AstNode[]>(() => [])
for (let [name, css] of entries) {
if (name.startsWith('@keyframes ')) {
if (!referenceMode) {
let keyframes = rule(name, objectToAst(css))
walk([keyframes], (node) => {
node.src = src
})
ast.push(keyframes)
}
continue
}
let selectorAst = SelectorParser.parse(name)
let foundValidUtility = false
walk(selectorAst, (node) => {
if (
node.kind === 'selector' &&
node.value[0] === '.' &&
IS_VALID_UTILITY_NAME.test(node.value.slice(1))
) {
let value = node.value
node.value = '&'
let selector = SelectorParser.toCss(selectorAst)
let className = value.slice(1)
let contents = selector === '&' ? objectToAst(css) : [rule(selector, objectToAst(css))]
utils.get(className).push(...contents)
foundValidUtility = true
node.value = value
return
}
if (node.kind === 'function' && node.value === ':not') {
return WalkAction.Skip
}
})
if (!foundValidUtility) {
throw new Error(
`\`addUtilities({ '${name}' : … })\` defines an invalid utility selector. Utilities must be a single class name and start with a lowercase letter, eg. \`.scrollbar-none\`.`,
)
}
}
for (let [className, ast] of utils) {
// Prefix all class selector with the configured theme prefix
if (designSystem.theme.prefix) {
walk(ast, (node) => {
if (node.kind === 'rule') {
let selectorAst = SelectorParser.parse(node.selector)
walk(selectorAst, (node) => {
if (node.kind === 'selector' && node.value[0] === '.') {
node.value = `.${designSystem.theme.prefix}\\:${node.value.slice(1)}`
}
})
node.selector = SelectorParser.toCss(selectorAst)
}
})
}
designSystem.utilities.static(className, (candidate) => {
let clonedAst = ast.map(cloneAstNode)
replaceNestedClassNameReferences(clonedAst, className, candidate.raw)
featuresRef.current |= substituteAtApply(clonedAst, designSystem)
return clonedAst
})
}
},
matchUtilities(utilities, options) {
let types = options?.type
? Array.isArray(options?.type)
? options.type
: [options.type]
: ['any']
for (let [name, fn] of Object.entries(utilities)) {
if (!IS_VALID_UTILITY_NAME.test(name)) {
throw new Error(
`\`matchUtilities({ '${name}' : … })\` defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter, eg. \`scrollbar\`.`,
)
}
function compileFn({ negative }: { negative: boolean }) {
return (candidate: Extract<Candidate, { kind: 'functional' }>) => {
// Throw out any candidate whose value is not a supported type
if (
candidate.value?.kind === 'arbitrary' &&
types.length > 0 &&
!types.includes('any')
) {
// The candidate has an explicit data type but it's not in the list
// of supported types by this utility. For example, a `scrollbar`
// utility that is only used to change the scrollbar color but is
// used with a `length` value: `scrollbar-[length:var(--whatever)]`
if (candidate.value.dataType && !types.includes(candidate.value.dataType)) {
return
}
// The candidate does not have an explicit data type and the value
// cannot be inferred as one of the supported types. For example, a
// `scrollbar` utility that is only used to change the scrollbar
// color but is used with a `length` value: `scrollbar-[33px]`
if (
!candidate.value.dataType &&
!inferDataType(candidate.value.value, types as any[])
) {
return
}
}
let isColor = types.includes('color')
// Resolve the candidate value
let value: string | null = null
let ignoreModifier = false
{
let values = options?.values ?? {}
if (isColor) {
// Color utilities implicitly support `inherit`, `transparent`, and `currentcolor`
// for backwards compatibility but still allow them to be overridden
values = Object.assign(
{
inherit: 'inherit',
transparent: 'transparent',
current: 'currentcolor',
},
values,
)
}
if (!candidate.value) {
value = values.DEFAULT ?? null
} else if (candidate.value.kind === 'arbitrary') {
value = candidate.value.value
} else if (candidate.value.fraction && values[candidate.value.fraction]) {
value = values[candidate.value.fraction]
ignoreModifier = true
} else if (values[candidate.value.value]) {
value = values[candidate.value.value]
} else if (values.__BARE_VALUE__) {
value = values.__BARE_VALUE__(candidate.value) ?? null
ignoreModifier =
(candidate.value.fraction !== null && value?.includes('/')) ?? false
}
}
if (value === null) return
// Resolve the modifier value
let modifier: string | null
{
let modifiers = options?.modifiers ?? null
if (!candidate.modifier) {
modifier = null
} else if (modifiers === 'any' || candidate.modifier.kind === 'arbitrary') {
modifier = candidate.modifier.value
} else if (modifiers?.[candidate.modifier.value]) {
modifier = modifiers[candidate.modifier.value]
} else if (isColor && !Number.isNaN(Number(candidate.modifier.value))) {
modifier = `${candidate.modifier.value}%`
} else {
modifier = null
}
}
// A modifier was provided but is invalid
if (candidate.modifier && modifier === null && !ignoreModifier) {
// For arbitrary values, return `null` to avoid falling through to the next utility
return candidate.value?.kind === 'arbitrary' ? null : undefined
}
if (isColor && modifier !== null) {
value = withAlpha(value, modifier)
}
if (negative) {
value = `calc(${value} * -1)`
}
let ast = objectToAst(fn(value, { modifier }))
replaceNestedClassNameReferences(ast, name, candidate.raw)
featuresRef.current |= substituteAtApply(ast, designSystem)
return ast
}
}
if (options?.supportsNegativeValues) {
designSystem.utilities.functional(`-${name}`, compileFn({ negative: true }), { types })
}
designSystem.utilities.functional(name, compileFn({ negative: false }), { types })
designSystem.utilities.suggest(name, () => {
let values = options?.values ?? {}
let valueKeys = new Set<string | null>(Object.keys(values))
// The `__BARE_VALUE__` key is a special key used to ensure bare values
// work even with legacy configs and plugins
valueKeys.delete('__BARE_VALUE__')
// The `__CSS_VALUES__` key is a special key used by the theme function
// to transport `@theme` metadata about values from CSS
valueKeys.delete('__CSS_VALUES__')
// The `DEFAULT` key is represented as `null` in the utility API
if (valueKeys.has('DEFAULT')) {
valueKeys.delete('DEFAULT')
valueKeys.add(null)
}
let modifiers = options?.modifiers ?? {}
let modifierKeys = modifiers === 'any' ? [] : Object.keys(modifiers)
return [
{
supportsNegative: options?.supportsNegativeValues ?? false,
values: Array.from(valueKeys),
modifiers: modifierKeys,
},
]
})
}
},
addComponents(components, options) {
this.addUtilities(components, options)
},
matchComponents(components, options) {
this.matchUtilities(components, options)
},
theme: createThemeFn(
designSystem,
() => resolvedConfig.theme ?? {},
(value) => value,
),
prefix(className) {
return className
},
config(path, defaultValue) {
let obj: Record<any, any> = resolvedConfig
if (!path) return obj
let keypath = toKeyPath(path)
for (let i = 0; i < keypath.length; ++i) {
let key = keypath[i]
if (obj[key] === undefined) return defaultValue
obj = obj[key]
}
return obj ?? defaultValue
},
}
// Bind these functions so they can use `this`
api.addComponents = api.addComponents.bind(api)
api.matchComponents = api.matchComponents.bind(api)
return api
}
Domain
Subdomains
Calls
Called By
Source
Frequently Asked Questions
What does buildPluginApi() do?
buildPluginApi() is a function in the tailwindcss codebase.
What does buildPluginApi() call?
buildPluginApi() calls 24 function(s): atRule, compoundsForSelectors, createThemeFn, entries, fromAst, functional, get, group, and 16 more.
What calls buildPluginApi()?
buildPluginApi() is called by 1 function(s): upgradeToFullPluginSupport.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free