Css attribute pruning is not case insensitive
#17207
Development PR
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:
- The selector got incorrectly flagged as unused (no
css_unused_selectorwarning was shown when spreads were involved, but the selector was still pruned) - The scoping class wasn't applied to the matching element
- 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
<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-insensitiveCSS test coveringform[method]andinput[type]cases - All 179 existing CSS tests pass
- Verified the existing
attribute-selector-case-sensitivetest (usingsflag) still works correctly - Compiler error tests and validator tests all pass
Issue
Describe the bug
<script lang="ts">
import { logout } from "./logout.remote";
</script>
<!-- Adding this form element -->
<form {...logout}></form>
<!-- 1. Does not add `svelte-*` class name in build output -->
<form method="GET">
<h1>Hello, World!</h1>
</form>
<style lang="postcss">
/* 2. Disables the following warning: */
/* Unused CSS selector "form[method="get"] :global" */
form[method="get"] :global {
h1 {
color: red;
}
}
</style>
In vite preview, the markup is:
<form method="POST" action="?/remote=" class="svelte-1uha8ag"></form>
<form method="GET"><h1>Hello, World!</h1></form>
- The
h1element is red in dev only (due to lack ofsvelte-*class name) - The unused CSS warning is incorrect (which is why it is red in dev mode)
The case sensitivity of attribute names and values depends on the document language. In HTML, attribute names are case-insensitive, as are spec-defined values. —
Reproduction
https://github.com/hyunbinseo/svelte-kit-14963
System Info
System:
OS: Windows 11 10.0.26200
CPU: (8) x64 Intel(R) Core(TM) Ultra 7 258V
Memory: 4.66 GB / 31.48 GB
Binaries:
Node: 24.11.0 - C:\Users\hyunb\AppData\Local\fnm_multishells\25576_1763723570534\node.EXE
Yarn: 1.22.22 - C:\Users\hyunb\AppData\Local\fnm_multishells\25576_1763723570534\yarn.CMD
npm: 11.6.1 - C:\Users\hyunb\AppData\Local\fnm_multishells\25576_1763723570534\npm.CMD
pnpm: 10.23.0 - C:\Users\hyunb\AppData\Local\fnm_multishells\25576_1763723570534\pnpm.CMD
Deno: 2.4.0 - C:\Users\hyunb\.deno\bin\deno.EXE
Browsers:
Chrome: 142.0.7444.164
Edge: Chromium (140.0.3485.54)
Firefox: 145.0.1 - C:\Program Files\Mozilla Firefox\firefox.exe
npmPackages:
@sveltejs/adapter-auto: ^7.0.0 => 7.0.0
@sveltejs/kit: ^2.47.1 => 2.49.0
@sveltejs/vite-plugin-svelte: ^6.2.1 => 6.2.1
svelte: ^5.41.0 => 5.43.14
vite: ^7.1.10 => 7.2.4
Severity
serious, but I can work around it
Additional Information
No response
Info
This has nothing to do with remote functions...is just svelte and the wrong pruning of css which in turns doesn't add the svelte class name because the element is not targeted
https://svelte.dev/playground/f185b22d8ca041c89340c8073d646604?version=5.43.14
Moved the issue to svelte and changed the name, thanks for reporting
Thanks for the quick review, but what about no. 2?
The lack of warning was what made the debugging difficult.
Why is it not shown after the SvelteKit remote function was added?
[!NOTE] The
svelte-*class name does exist in the CSS output:form[method=get].svelte-1uha8ag h1{color:red}
That's because you are spreading an object into a separate form which means svelte can't know if that form has the right attribute or not
Pro tip: You can prefix GitHub URLs of issues, PRs or discussions with svcl.dev/ to view them on this page! Also try it on a GitHub release ;)