fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes (#17712)

Fixes #17207

CSS attribute selectors for HTML enumerated attributes (like `method`,
`type`, `dir`, etc.) are supposed to match case-insensitively per the
HTML spec. Browsers handle this correctly — `form[method="get"]` matches
`<form method="GET">`. But Svelte's CSS pruning was doing a strict
case-sensitive comparison, which meant:

1. The selector got incorrectly flagged as unused (no
`css_unused_selector` warning was shown when spreads were involved, but
the selector was still pruned)
2. The scoping class wasn't applied to the matching element
3. Styles silently disappeared in production builds

The fix adds a set of known HTML attributes with case-insensitive
enumerated values (sourced from the HTML spec) and uses it during CSS
attribute selector matching. The explicit CSS `s` flag still overrides
this behavior, as expected.

### Before
```svelte
<form method="GET">
  <h1>Hello</h1>
</form>

<style>
  form[method="get"] h1 { color: red; }
  /* ^ incorrectly pruned, <h1> not styled */
</style>
```

### After
The selector correctly matches and styles are applied.

### Test plan
- Added `attribute-selector-html-case-insensitive` CSS test covering
`form[method]` and `input[type]` cases
- All 179 existing CSS tests pass
- Verified the existing `attribute-selector-case-sensitive` test (using
`s` flag) still works correctly
- Compiler error tests and validator tests all pass

---------

Co-authored-by: Rich Harris <hello@rich-harris.dev>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/17725/head
Varun Chawla 3 days ago committed by GitHub
parent 6557a0a591
commit 60a425193c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes

@ -22,6 +22,50 @@ const whitelist_attribute_selector = new Map([
['dialog', ['open']]
]);
/**
* HTML attributes whose enumerated values are case-insensitive per the HTML spec.
* CSS attribute selectors match these values case-insensitively in HTML documents.
* @see {@link https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors HTML spec}
*/
const case_insensitive_attributes = new Set([
'accept-charset',
'autocapitalize',
'autocomplete',
'behavior',
'charset',
'crossorigin',
'decoding',
'dir',
'direction',
'draggable',
'enctype',
'enterkeyhint',
'fetchpriority',
'formenctype',
'formmethod',
'formtarget',
'hidden',
'http-equiv',
'inputmode',
'kind',
'loading',
'method',
'preload',
'referrerpolicy',
'rel',
'rev',
'role',
'rules',
'scope',
'shape',
'spellcheck',
'target',
'translate',
'type',
'valign',
'wrap'
]);
/** @type {Compiler.AST.CSS.Combinator} */
const descendant_combinator = {
type: 'Combinator',
@ -523,7 +567,9 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
selector.name,
selector.value && unquote(selector.value),
selector.matcher,
selector.flags?.includes('i') ?? false
(selector.flags?.includes('i') ?? false) ||
(!selector.flags?.includes('s') &&
case_insensitive_attributes.has(selector.name.toLowerCase()))
)
) {
return false;

@ -0,0 +1,11 @@
form[method="get"].svelte-xyz h1:where(.svelte-xyz) {
color: red;
}
form[method="post"].svelte-xyz h1:where(.svelte-xyz) {
color: blue;
}
input[type="text"].svelte-xyz {
color: green;
}

@ -0,0 +1 @@
<form class="svelte-xyz" method="GET"><h1 class="svelte-xyz">Hello</h1></form> <form class="svelte-xyz" method="POST"><h1 class="svelte-xyz">World</h1></form> <input class="svelte-xyz" type="Text" />

@ -0,0 +1,23 @@
<form method="GET">
<h1>Hello</h1>
</form>
<form method="POST">
<h1>World</h1>
</form>
<input type="Text" />
<style>
form[method="get"] h1 {
color: red;
}
form[method="post"] h1 {
color: blue;
}
input[type="text"] {
color: green;
}
</style>
Loading…
Cancel
Save