createVariants() — tailwindcss Function Reference
Architecture documentation for the createVariants() function in variants.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD c067616f_e276_458d_2166_c9f10e6fcc93["createVariants()"] c58d3214_88d6_f4fc_257f_8e84def5b24f["buildDesignSystem()"] c58d3214_88d6_f4fc_257f_8e84def5b24f -->|calls| c067616f_e276_458d_2166_c9f10e6fcc93 3a207375_0c4d_0a9e_f1f0_05b2b56ac8a7["compoundsForSelectors()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| 3a207375_0c4d_0a9e_f1f0_05b2b56ac8a7 f4f92a3d_c13e_a751_8402_451ffa4c772f["rule()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| f4f92a3d_c13e_a751_8402_451ffa4c772f 03b8d706_a876_a776_0056_186ced5d6067["segment()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| 03b8d706_a876_a776_0056_186ced5d6067 c35acfc6_964d_737e_6ecc_275e6f10293a["atRule()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| c35acfc6_964d_737e_6ecc_275e6f10293a 7a418a11_25ee_a4c5_13e5_b5effc024d85["compound()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| 7a418a11_25ee_a4c5_13e5_b5effc024d85 a32bba76_f60d_883f_1ff1_276a0bb9db9f["walk()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| a32bba76_f60d_883f_1ff1_276a0bb9db9f 9db2a11b_4852_e036_e394_622c7a90dd1f["styleRule()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| 9db2a11b_4852_e036_e394_622c7a90dd1f e14c7a80_fdf6_db8c_e557_2172bede4092["suggest()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| e14c7a80_fdf6_db8c_e557_2172bede4092 9b70b4cf_ee2b_1735_5367_7b42cfc55b57["keys()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| 9b70b4cf_ee2b_1735_5367_7b42cfc55b57 fd3adc32_3109_75f1_7942_d8cb145512d7["compoundsWith()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| fd3adc32_3109_75f1_7942_d8cb145512d7 398eed5f_f4c5_4768_fc95_4c1df31be1f8["atRoot()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| 398eed5f_f4c5_4768_fc95_4c1df31be1f8 47ef7bd7_b959_59c4_dbd1_328f35d7cd89["decl()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| 47ef7bd7_b959_59c4_dbd1_328f35d7cd89 bd2fd3ca_e76d_ae00_2925_6f523cc4a134["has()"] c067616f_e276_458d_2166_c9f10e6fcc93 -->|calls| bd2fd3ca_e76d_ae00_2925_6f523cc4a134 style c067616f_e276_458d_2166_c9f10e6fcc93 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
}
Domain
Subdomains
Calls
Called By
Source
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