From 60a425193c643665c99dfd7e0d5cea5b052127a9 Mon Sep 17 00:00:00 2001
From: Varun Chawla <34209028+veeceey@users.noreply.github.com>
Date: Tue, 17 Feb 2026 06:54:15 -0800
Subject: [PATCH] fix: treat CSS attribute selectors as case-insensitive for
HTML enumerated attributes (#17712)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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
`
```
### 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
Co-authored-by: Rich Harris
---
.changeset/css-attribute-case-insensitive.md | 5 ++
.../phases/2-analyze/css/css-prune.js | 48 ++++++++++++++++++-
.../expected.css | 11 +++++
.../expected.html | 1 +
.../input.svelte | 23 +++++++++
5 files changed, 87 insertions(+), 1 deletion(-)
create mode 100644 .changeset/css-attribute-case-insensitive.md
create mode 100644 packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css
create mode 100644 packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html
create mode 100644 packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte
diff --git a/.changeset/css-attribute-case-insensitive.md b/.changeset/css-attribute-case-insensitive.md
new file mode 100644
index 0000000000..e5b3bcea2b
--- /dev/null
+++ b/.changeset/css-attribute-case-insensitive.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes
diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
index 7242c69d8c..7e05d2e7d3 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
@@ -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;
diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css
new file mode 100644
index 0000000000..01ec1b2269
--- /dev/null
+++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css
@@ -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;
+ }
diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html
new file mode 100644
index 0000000000..101c03f836
--- /dev/null
+++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html
@@ -0,0 +1 @@
+
diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte
new file mode 100644
index 0000000000..e11a922d75
--- /dev/null
+++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+