Skip to content

Commit 2030e94

Browse files
Don't consider the global important state in @apply (#18404)
Fixes #18400 In v3 when you used `important: true` it did not affect `@apply`. However, in v4 it does and there's no way to make it *not*. This is definitely a bug and would be unexpected for users coming from v3 who use `@apply` and `important` together. Basically, the following code, along with the detected utility `flex` in source files… ```css @import 'tailwindcss/utilities' important; .flex-explicitly-important { @apply flex!; } .flex-not-important { @apply flex; } ``` … would output this: ```css .flex { display: flex !important; } .flex-explicitly-important { display: flex !important; } .flex-not-important { display: flex !important; } ``` But it's expected that `@apply` doesn't consider the "global" important state. This PR addresss this problem and now the output is this: ```css .flex { display: flex !important; } .flex-explicitly-important { display: flex !important; } .flex-not-important { display: flex; /* this line changed */ } ``` If you want to mark a utility as important in `@apply` you can still use `!` after the utility to do so as shown above. --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
1 parent b716d10 commit 2030e94

File tree

6 files changed

+103
-31
lines changed

6 files changed

+103
-31
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
- Nothing yet!
10+
### Fixed
11+
12+
- Don't consider the global important state in `@apply` ([#18404](https://github.com/tailwindlabs/tailwindcss/pull/18404))
1113

1214
## [4.1.11] - 2025-06-26
1315

packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { substituteAtApply } from '../../../../tailwindcss/src/apply'
22
import { atRule, styleRule, toCss, walk, type AstNode } from '../../../../tailwindcss/src/ast'
33
import { printArbitraryValue } from '../../../../tailwindcss/src/candidate'
44
import * as SelectorParser from '../../../../tailwindcss/src/compat/selector-parser'
5-
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
5+
import { CompileAstFlags, type DesignSystem } from '../../../../tailwindcss/src/design-system'
66
import { ThemeOptions } from '../../../../tailwindcss/src/theme'
77
import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map'
88
import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infer-data-type'
@@ -40,7 +40,16 @@ export const computeUtilitySignature = new DefaultMap<
4040
// Use `@apply` to normalize the selector to `.x`
4141
let ast: AstNode[] = [styleRule('.x', [atRule('@apply', utility)])]
4242

43-
temporarilyDisableThemeInline(designSystem, () => substituteAtApply(ast, designSystem))
43+
temporarilyDisableThemeInline(designSystem, () => {
44+
// There's separate utility caches for respect important vs not
45+
// so we want to compile them both with `@theme inline` disabled
46+
for (let candidate of designSystem.parseCandidate(utility)) {
47+
designSystem.compileAstNodes(candidate, CompileAstFlags.None)
48+
designSystem.compileAstNodes(candidate, CompileAstFlags.RespectImportant)
49+
}
50+
51+
substituteAtApply(ast, designSystem)
52+
})
4453

4554
// We will be mutating the AST, so we need to clone it first to not affect
4655
// the original AST

packages/tailwindcss/src/apply.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
176176
// with.
177177
let candidates = Object.keys(candidateOffsets)
178178
let compiled = compileCandidates(candidates, designSystem, {
179+
respectImportant: false,
179180
onInvalidCandidate: (candidate) => {
180181
// When using prefix, make sure prefix is used in candidate
181182
if (designSystem.theme.prefix && !candidate.startsWith(designSystem.theme.prefix)) {

packages/tailwindcss/src/compile.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
type StyleRule,
1010
} from './ast'
1111
import { type Candidate, type Variant } from './candidate'
12-
import { type DesignSystem } from './design-system'
12+
import { CompileAstFlags, type DesignSystem } from './design-system'
1313
import GLOBAL_PROPERTY_ORDER from './property-order'
1414
import { asColor, type Utility } from './utilities'
1515
import { compare } from './utils/compare'
@@ -19,7 +19,10 @@ import type { Variants } from './variants'
1919
export function compileCandidates(
2020
rawCandidates: Iterable<string>,
2121
designSystem: DesignSystem,
22-
{ onInvalidCandidate }: { onInvalidCandidate?: (candidate: string) => void } = {},
22+
{
23+
onInvalidCandidate,
24+
respectImportant,
25+
}: { onInvalidCandidate?: (candidate: string) => void; respectImportant?: boolean } = {},
2326
) {
2427
let nodeSorting = new Map<
2528
AstNode,
@@ -44,14 +47,20 @@ export function compileCandidates(
4447
matches.set(rawCandidate, candidates)
4548
}
4649

50+
let flags = CompileAstFlags.None
51+
52+
if (respectImportant ?? true) {
53+
flags |= CompileAstFlags.RespectImportant
54+
}
55+
4756
let variantOrderMap = designSystem.getVariantOrder()
4857

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

5362
for (let candidate of candidates) {
54-
let rules = designSystem.compileAstNodes(candidate)
63+
let rules = designSystem.compileAstNodes(candidate, flags)
5564
if (rules.length === 0) continue
5665

5766
found = true
@@ -119,10 +128,16 @@ export function compileCandidates(
119128
}
120129
}
121130

122-
export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem) {
131+
export function compileAstNodes(
132+
candidate: Candidate,
133+
designSystem: DesignSystem,
134+
flags: CompileAstFlags,
135+
) {
123136
let asts = compileBaseUtility(candidate, designSystem)
124137
if (asts.length === 0) return []
125138

139+
let respectImportant = designSystem.important && Boolean(flags & CompileAstFlags.RespectImportant)
140+
126141
let rules: {
127142
node: AstNode
128143
propertySort: {
@@ -136,7 +151,10 @@ export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem
136151
for (let nodes of asts) {
137152
let propertySort = getPropertySort(nodes)
138153

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

packages/tailwindcss/src/design-system.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import { DefaultMap } from './utils/default-map'
1818
import { extractUsedVariables } from './utils/variables'
1919
import { Variants, createVariants } from './variants'
2020

21+
export const enum CompileAstFlags {
22+
None = 0,
23+
RespectImportant = 1 << 0,
24+
}
25+
2126
export type DesignSystem = {
2227
theme: Theme
2328
utilities: Utilities
@@ -34,7 +39,7 @@ export type DesignSystem = {
3439

3540
parseCandidate(candidate: string): Readonly<Candidate>[]
3641
parseVariant(variant: string): Readonly<Variant> | null
37-
compileAstNodes(candidate: Candidate): ReturnType<typeof compileAstNodes>
42+
compileAstNodes(candidate: Candidate, flags?: CompileAstFlags): ReturnType<typeof compileAstNodes>
3843

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

60-
let compiledAstNodes = new DefaultMap<Candidate>((candidate) => {
61-
let ast = compileAstNodes(candidate, designSystem)
62-
63-
// Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary
64-
// properties (`[--my-var:theme(--color-red-500)]`) can contain function
65-
// calls so we need evaluate any functions we find there that weren't in
66-
// the source CSS.
67-
try {
68-
substituteFunctions(
69-
ast.map(({ node }) => node),
70-
designSystem,
71-
)
72-
} catch (err) {
73-
// If substitution fails then the candidate likely contains a call to
74-
// `theme()` that is invalid which may be because of incorrect usage,
75-
// invalid arguments, or a theme key that does not exist.
76-
return []
77-
}
65+
let compiledAstNodes = new DefaultMap<number>((flags) => {
66+
return new DefaultMap<Candidate>((candidate) => {
67+
let ast = compileAstNodes(candidate, designSystem, flags)
68+
69+
// Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary
70+
// properties (`[--my-var:theme(--color-red-500)]`) can contain function
71+
// calls so we need evaluate any functions we find there that weren't in
72+
// the source CSS.
73+
try {
74+
substituteFunctions(
75+
ast.map(({ node }) => node),
76+
designSystem,
77+
)
78+
} catch (err) {
79+
// If substitution fails then the candidate likely contains a call to
80+
// `theme()` that is invalid which may be because of incorrect usage,
81+
// invalid arguments, or a theme key that does not exist.
82+
return []
83+
}
7884

79-
return ast
85+
return ast
86+
})
8087
})
8188

8289
let trackUsedVariables = new DefaultMap((raw) => {
@@ -134,8 +141,8 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
134141
parseVariant(variant: string) {
135142
return parsedVariants.get(variant)
136143
},
137-
compileAstNodes(candidate: Candidate) {
138-
return compiledAstNodes.get(candidate)
144+
compileAstNodes(candidate: Candidate, flags = CompileAstFlags.RespectImportant) {
145+
return compiledAstNodes.get(flags).get(candidate)
139146
},
140147

141148
printCandidate(candidate: Candidate) {

packages/tailwindcss/src/index.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ describe('@apply', () => {
666666
})
667667

668668
// https://github.com/tailwindlabs/tailwindcss/issues/16935
669-
it('should now swallow @utility declarations when @apply is used in nested rules', async () => {
669+
it('should not swallow @utility declarations when @apply is used in nested rules', async () => {
670670
expect(
671671
await compileCss(
672672
css`
@@ -777,6 +777,41 @@ describe('@apply', () => {
777777
}"
778778
`)
779779
})
780+
781+
// https://github.com/tailwindlabs/tailwindcss/issues/18400
782+
it('should ignore the design systems `important` flag when using @apply', async () => {
783+
expect(
784+
await compileCss(
785+
css`
786+
@import 'tailwindcss/utilities' important;
787+
.flex-explicitly-important {
788+
@apply flex!;
789+
}
790+
.flex-not-important {
791+
@apply flex;
792+
}
793+
`,
794+
['flex'],
795+
{
796+
async loadStylesheet(_, base) {
797+
return {
798+
content: '@tailwind utilities;',
799+
base,
800+
path: '',
801+
}
802+
},
803+
},
804+
),
805+
).toMatchInlineSnapshot(`
806+
".flex, .flex-explicitly-important {
807+
display: flex !important;
808+
}
809+
810+
.flex-not-important {
811+
display: flex;
812+
}"
813+
`)
814+
})
780815
})
781816

782817
describe('arbitrary variants', () => {

0 commit comments

Comments
 (0)