What version of Tailwind CSS are you using?
v4.3.3 (@tailwindcss/oxide 4.3.3)
What build tool (or framework if it abstracts the build tool) are you using?
Vite 8 with @tailwindcss/vite, Vue 3 SFCs with <template lang="pug">; reproduced with the bare Oxide Scanner too.
Reproduction
broken.pug:
p
| (
span.underline.font-bold(title="x") km
| )
working.pug (same, but the text lines contain no bracket):
p
| x
span.underline.font-bold(title="x") km
| x
const {Scanner} = require('@tailwindcss/oxide')
for (const f of ['broken.pug', 'working.pug'])
console.log(f, new Scanner({sources: [{base: __dirname, pattern: f, negated: false}]}).scan()
.filter(c => ['underline', 'font-bold'].includes(c)))
broken.pug []
working.pug [ 'font-bold', 'underline' ]
Describe your issue
A Pug text line containing an unbalanced opening bracket (here | (, which renders a literal "(") stops the
class shorthand on the next tag from being extracted, so those utilities silently get no CSS. Pug itself
renders the template correctly. A real-world case is wrapping a button in parentheses: | (, then
v-btn.px-0.normal-case(...), then | ).
The cause appears to be the bracket tracking in crates/oxide/src/extractor/pre_processors/pug.rs: the
BracketStack is kept across the whole file, so the ( in the text line is pushed and never matched.
While the stack is non-empty, . is no longer replaced with a space, so span.underline.font-bold stays a
single, invalid candidate. The tag's own attribute parens don't help: its ) pops only its own (.
The same happens inside Vue SFCs (<template lang="pug">), where only some of the classes are extracted.
A comment line behaves the same way: //- a bare "(" here before the tag reproduces it too.
Workaround: write the bracket as an HTML entity (| () or keep brackets balanced on the text line.
Possible fix: reset the bracket stack at line boundaries unless the line is inside an attribute list
(Pug attribute lists may span lines, so a plain per-line reset is not enough), or skip piped text (| ...)
and comment (//, //-) lines in the pre-processor.
What version of Tailwind CSS are you using?
v4.3.3 (
@tailwindcss/oxide4.3.3)What build tool (or framework if it abstracts the build tool) are you using?
Vite 8 with
@tailwindcss/vite, Vue 3 SFCs with<template lang="pug">; reproduced with the bare OxideScannertoo.Reproduction
broken.pug:working.pug(same, but the text lines contain no bracket):Describe your issue
A Pug text line containing an unbalanced opening bracket (here
| (, which renders a literal "(") stops theclass shorthand on the next tag from being extracted, so those utilities silently get no CSS. Pug itself
renders the template correctly. A real-world case is wrapping a button in parentheses:
| (, thenv-btn.px-0.normal-case(...), then| ).The cause appears to be the bracket tracking in
crates/oxide/src/extractor/pre_processors/pug.rs: theBracketStackis kept across the whole file, so the(in the text line is pushed and never matched.While the stack is non-empty,
.is no longer replaced with a space, sospan.underline.font-boldstays asingle, invalid candidate. The tag's own attribute parens don't help: its
)pops only its own(.The same happens inside Vue SFCs (
<template lang="pug">), where only some of the classes are extracted.A comment line behaves the same way:
//- a bare "(" herebefore the tag reproduces it too.Workaround: write the bracket as an HTML entity (
| () or keep brackets balanced on the text line.Possible fix: reset the bracket stack at line boundaries unless the line is inside an attribute list
(Pug attribute lists may span lines, so a plain per-line reset is not enough), or skip piped text (
| ...)and comment (
//,//-) lines in the pre-processor.