Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Ensure `not-*` does not remove `:is(…)` from variants ([#16825](https://github.com/tailwindlabs/tailwindcss/pull/16825))
- Ensure `@keyframes` are correctly emitted when using a prefixed setup ([#16850](https://github.com/tailwindlabs/tailwindcss/pull/16850))
- Don't swallow `@utility` declarations when `@apply` is used in nested rules ([#16940](https://github.com/tailwindlabs/tailwindcss/pull/16940))

## [4.0.9] - 2025-02-25

Expand Down
64 changes: 35 additions & 29 deletions packages/tailwindcss/src/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,39 +146,45 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
visit(node)
}

// Substitute the `@apply` at-rules in order
walk(sorted, (node, { replaceWith }) => {
if (node.kind !== 'at-rule' || node.name !== '@apply') return
let candidates = node.params.split(/\s+/g)

// Replace the `@apply` rule with the actual utility classes
{
// Parse the candidates to an AST that we can replace the `@apply` rule
// with.
let candidateAst = compileCandidates(candidates, designSystem, {
onInvalidCandidate: (candidate) => {
throw new Error(`Cannot apply unknown utility class: ${candidate}`)
},
}).astNodes

// Collect the nodes to insert in place of the `@apply` rule. When a rule
// was used, we want to insert its children instead of the rule because we
// don't want the wrapping selector.
let newNodes: AstNode[] = []
for (let candidateNode of candidateAst) {
if (candidateNode.kind === 'rule') {
for (let child of candidateNode.nodes) {
newNodes.push(child)
// Substitute the `@apply` at-rules in order. Note that the list is going to
// be flattened so we do not have to recursively walk over child rules
for (let parent of sorted) {
if (!('nodes' in parent)) continue

for (let i = 0; i < parent.nodes.length; i++) {
let node = parent.nodes[i]
if (node.kind !== 'at-rule' || node.name !== '@apply') continue

let candidates = node.params.split(/\s+/g)

// Replace the `@apply` rule with the actual utility classes
{
// Parse the candidates to an AST that we can replace the `@apply` rule
// with.
let candidateAst = compileCandidates(candidates, designSystem, {
onInvalidCandidate: (candidate) => {
throw new Error(`Cannot apply unknown utility class: ${candidate}`)
},
}).astNodes

// Collect the nodes to insert in place of the `@apply` rule. When a rule
// was used, we want to insert its children instead of the rule because we
// don't want the wrapping selector.
let newNodes: AstNode[] = []
for (let candidateNode of candidateAst) {
if (candidateNode.kind === 'rule') {
for (let child of candidateNode.nodes) {
newNodes.push(child)
}
} else {
newNodes.push(candidateNode)
}
} else {
newNodes.push(candidateNode)
}
}

replaceWith(newNodes)
parent.nodes.splice(i, 1, ...newNodes)
}
}
})

}
return features
}

Expand Down
35 changes: 35 additions & 0 deletions packages/tailwindcss/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,41 @@ describe('@apply', () => {
}"
`)
})

// https://github.com/tailwindlabs/tailwindcss/issues/16935
it('should now swallow @utility declarations when @apply is used in nested rules', async () => {
expect(
await compileCss(
css`
@tailwind utilities;

.ignore-me {
@apply underline;
div {
@apply custom-utility;
}
}

@utility custom-utility {
@apply flex;
}
`,
['custom-utility'],
),
).toMatchInlineSnapshot(`
".custom-utility {
display: flex;
}

.ignore-me {
text-decoration-line: underline;
}

.ignore-me div {
display: flex;
}"
`)
})
})

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