Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Nothing yet!
### Fixed

- Don't consider the global important state in `@apply` ([#18404](https://github.com/tailwindlabs/tailwindcss/pull/18404))

## [4.1.11] - 2025-06-26

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { substituteAtApply } from '../../../../tailwindcss/src/apply'
import { atRule, styleRule, toCss, walk, type AstNode } from '../../../../tailwindcss/src/ast'
import { printArbitraryValue } from '../../../../tailwindcss/src/candidate'
import * as SelectorParser from '../../../../tailwindcss/src/compat/selector-parser'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { CompileAstFlags, type DesignSystem } from '../../../../tailwindcss/src/design-system'
import { ThemeOptions } from '../../../../tailwindcss/src/theme'
import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map'
import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infer-data-type'
Expand Down Expand Up @@ -40,7 +40,16 @@ export const computeUtilitySignature = new DefaultMap<
// Use `@apply` to normalize the selector to `.x`
let ast: AstNode[] = [styleRule('.x', [atRule('@apply', utility)])]

temporarilyDisableThemeInline(designSystem, () => substituteAtApply(ast, designSystem))
temporarilyDisableThemeInline(designSystem, () => {
// There's separate utility caches for respect important vs not
// so we want to compile them both with `@theme inline` disabled
for (let candidate of designSystem.parseCandidate(utility)) {
designSystem.compileAstNodes(candidate, CompileAstFlags.None)
designSystem.compileAstNodes(candidate, CompileAstFlags.RespectImportant)
}

substituteAtApply(ast, designSystem)
})

// We will be mutating the AST, so we need to clone it first to not affect
// the original AST
Expand Down
1 change: 1 addition & 0 deletions packages/tailwindcss/src/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
// with.
let candidates = Object.keys(candidateOffsets)
let compiled = compileCandidates(candidates, designSystem, {
respectImportant: false,
onInvalidCandidate: (candidate) => {
// When using prefix, make sure prefix is used in candidate
if (designSystem.theme.prefix && !candidate.startsWith(designSystem.theme.prefix)) {
Expand Down
28 changes: 23 additions & 5 deletions packages/tailwindcss/src/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type StyleRule,
} from './ast'
import { type Candidate, type Variant } from './candidate'
import { type DesignSystem } from './design-system'
import { CompileAstFlags, type DesignSystem } from './design-system'
import GLOBAL_PROPERTY_ORDER from './property-order'
import { asColor, type Utility } from './utilities'
import { compare } from './utils/compare'
Expand All @@ -19,7 +19,10 @@ import type { Variants } from './variants'
export function compileCandidates(
rawCandidates: Iterable<string>,
designSystem: DesignSystem,
{ onInvalidCandidate }: { onInvalidCandidate?: (candidate: string) => void } = {},
{
onInvalidCandidate,
respectImportant,
}: { onInvalidCandidate?: (candidate: string) => void; respectImportant?: boolean } = {},
) {
let nodeSorting = new Map<
AstNode,
Expand All @@ -44,14 +47,20 @@ export function compileCandidates(
matches.set(rawCandidate, candidates)
}

let flags = CompileAstFlags.None

if (respectImportant || respectImportant === undefined) {
flags |= CompileAstFlags.RespectImportant
}

let variantOrderMap = designSystem.getVariantOrder()

// Create the AST
for (let [rawCandidate, candidates] of matches) {
let found = false

for (let candidate of candidates) {
let rules = designSystem.compileAstNodes(candidate)
let rules = designSystem.compileAstNodes(candidate, flags)
if (rules.length === 0) continue

found = true
Expand Down Expand Up @@ -119,10 +128,16 @@ export function compileCandidates(
}
}

export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem) {
export function compileAstNodes(
candidate: Candidate,
designSystem: DesignSystem,
flags: CompileAstFlags,
) {
let asts = compileBaseUtility(candidate, designSystem)
if (asts.length === 0) return []

let respectImportant = Boolean(flags & CompileAstFlags.RespectImportant)

let rules: {
node: AstNode
propertySort: {
Expand All @@ -136,7 +151,10 @@ export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem
for (let nodes of asts) {
let propertySort = getPropertySort(nodes)

if (candidate.important || designSystem.important) {
// If the candidate itself is important then we want to always mark
// the utility as important. However, at a design system level we want
// to be able to opt-out when using things like `@apply`
if (candidate.important || (designSystem.important && respectImportant)) {
applyImportant(nodes)
}

Expand Down
53 changes: 31 additions & 22 deletions packages/tailwindcss/src/design-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import { DefaultMap } from './utils/default-map'
import { extractUsedVariables } from './utils/variables'
import { Variants, createVariants } from './variants'

export const enum CompileAstFlags {
None = 0,
RespectImportant = 1 << 0,
}

export type DesignSystem = {
theme: Theme
utilities: Utilities
Expand All @@ -34,7 +39,7 @@ export type DesignSystem = {

parseCandidate(candidate: string): Readonly<Candidate>[]
parseVariant(variant: string): Readonly<Variant> | null
compileAstNodes(candidate: Candidate): ReturnType<typeof compileAstNodes>
compileAstNodes(candidate: Candidate, flags?: CompileAstFlags): ReturnType<typeof compileAstNodes>

printCandidate(candidate: Candidate): string
printVariant(variant: Variant): string
Expand All @@ -57,26 +62,28 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
Array.from(parseCandidate(candidate, designSystem)),
)

let compiledAstNodes = new DefaultMap<Candidate>((candidate) => {
let ast = compileAstNodes(candidate, designSystem)

// Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary
// properties (`[--my-var:theme(--color-red-500)]`) can contain function
// calls so we need evaluate any functions we find there that weren't in
// the source CSS.
try {
substituteFunctions(
ast.map(({ node }) => node),
designSystem,
)
} catch (err) {
// If substitution fails then the candidate likely contains a call to
// `theme()` that is invalid which may be because of incorrect usage,
// invalid arguments, or a theme key that does not exist.
return []
}
let compiledAstNodes = new DefaultMap<number>((flags) => {
return new DefaultMap<Candidate>((candidate) => {
let ast = compileAstNodes(candidate, designSystem, flags)

// Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary
// properties (`[--my-var:theme(--color-red-500)]`) can contain function
// calls so we need evaluate any functions we find there that weren't in
// the source CSS.
try {
substituteFunctions(
ast.map(({ node }) => node),
designSystem,
)
} catch (err) {
// If substitution fails then the candidate likely contains a call to
// `theme()` that is invalid which may be because of incorrect usage,
// invalid arguments, or a theme key that does not exist.
return []
}

return ast
return ast
})
})

let trackUsedVariables = new DefaultMap((raw) => {
Expand Down Expand Up @@ -134,8 +141,10 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
parseVariant(variant: string) {
return parsedVariants.get(variant)
},
compileAstNodes(candidate: Candidate) {
return compiledAstNodes.get(candidate)
compileAstNodes(candidate: Candidate, flags?: CompileAstFlags) {
flags ??= CompileAstFlags.RespectImportant

return compiledAstNodes.get(flags).get(candidate)
},

printCandidate(candidate: Candidate) {
Expand Down
37 changes: 36 additions & 1 deletion packages/tailwindcss/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ describe('@apply', () => {
})

// https://github.com/tailwindlabs/tailwindcss/issues/16935
it('should now swallow @utility declarations when @apply is used in nested rules', async () => {
it('should not swallow @utility declarations when @apply is used in nested rules', async () => {
expect(
await compileCss(
css`
Expand Down Expand Up @@ -777,6 +777,41 @@ describe('@apply', () => {
}"
`)
})

// https://github.com/tailwindlabs/tailwindcss/issues/18400
it('should ignore the design systems `important` flag when using @apply', async () => {
expect(
await compileCss(
css`
@import 'tailwindcss/utilities' important;
.flex-explicitly-important {
@apply flex!;
}
.flex-not-important {
@apply flex;
}
`,
['flex'],
{
async loadStylesheet(_, base) {
return {
content: '@tailwind utilities;',
base,
path: '',
}
},
},
),
).toMatchInlineSnapshot(`
".flex, .flex-explicitly-important {
display: flex !important;
}

.flex-not-important {
display: flex;
}"
`)
})
})

describe('arbitrary variants', () => {
Expand Down