Put an i before the closing bracket of any CSS attribute selector and the value matches case-insensitively:
/* matches .pdf, .PDF, .Pdf, .pDf ... all of them */
a[href$=".pdf" i] {
background: url(/icons/pdf.svg) no-repeat left center;
padding-left: 1.4rem;
}That single letter is the whole trick. Without it, [href$=".pdf"] matches report.pdf but skips report.PDF, because attribute values are matched case-sensitively by default. The i flag tells the browser to compare the value the way a human reads it: PDF and pdf are the same string. This is exactly what you want when the case of the value is out of your hands, which on the web is most of the time.
Where it actually earns its place
I reach for [attr=value i] whenever I am styling something whose case I do not control:
- File extensions in links. A CMS, a user upload, or a third party hands you
.PDF,.Pdf, and.pdfinterchangeably.a[href$=".pdf" i]catches every variant with one rule instead of three. - URL schemes and hosts.
a[href^="mailto:" i]anda[href^="HTTPS:" i]both work regardless of how the markup was written. - User-entered or imported data. Anywhere an attribute value comes from a form, a spreadsheet import, or legacy content, the casing is inconsistent and you should not pretend otherwise.
- Data attributes you do not own. Styling on
[data-state="OPEN" i]survives a backend that flip-flops betweenopenandOPEN.
The alternative before this flag existed was either lowercasing the data at the source (often impossible) or writing the selector three times. The i flag replaces all of that.
One caveat worth knowing: case-insensitivity here is ASCII-only. It treats a-z and A-Z as equivalent, but it will not fold accented or non-Latin letters. For ASCII file extensions and URL schemes, which is the common case, that limitation never bites.
The flag works in JavaScript too
The same selector string is valid in querySelector and querySelectorAll, so the case-insensitive match carries straight over to the DOM API:
document.querySelectorAll('a[href$=".pdf" i]');That is genuinely useful: one selector, identical semantics, whether you are styling in CSS or collecting nodes in script.
The lesser-known s flag forces case-sensitive matching
There is a counterpart to i. An s before the closing bracket forces a case-sensitive match even in contexts where the document language would otherwise compare the value case-insensitively:
/* the type attribute on <ol> is normally compared case-insensitively;
s pins it to lowercase i only, not I */
ol[type="i" s] {
color: rebeccapurple;
}This is a niche tool. You only need s for the handful of attributes whose values HTML treats as case-insensitive by default (some enumerated attributes), where you specifically want to distinguish casing. For the overwhelming majority of attributes, matching is already case-sensitive without any flag, so s is redundant there. I have shipped i dozens of times and s essentially never, which is the right ratio.
A quick recap of the attribute-selector operators
The flag sits at the end of the bracket, after whichever match operator you used. The operators themselves are worth keeping straight:
| Selector | Matches when the attribute value... |
|---|---|
[attr] | exists at all, any value |
[attr=value] | equals value exactly |
[attr^=value] | starts with value |
[attr$=value] | ends with value |
[attr*=value] | contains value anywhere |
[attr~=value] | is a space-separated list containing value as one whole word |
| `[attr | =value]` |
Any of these takes the i or s flag in the same spot: [class~="Featured" i], [lang|="EN" i], [src*="THUMB" i].
Baseline support
The case-insensitive i flag is Baseline widely available: it has worked across Chrome, Firefox, Safari, and Edge for years (Chrome 49+, Firefox 47+, Safari 9+, Edge 79+), so you can use it in production without a fallback. The only holdouts are long-dead browsers like Internet Explorer and Opera Mini.
The s flag is the opposite story: as of mid-2026 it is not Baseline. Firefox shipped it (in 66) and is essentially the only engine that implements it; Chrome, Safari, and Edge still do not recognize it. Because an unrecognized modifier makes the whole selector fail to match rather than degrading gracefully, a [type="i" s] rule silently selects nothing in those browsers. So treat s as something you generally cannot rely on in production: reach for it only when forcing case sensitivity genuinely matters and you have controlled the browsers, and lean on plain (no-flag) selectors, which are already case-sensitive, everywhere else.
FAQ
See also
- The :has() relational selector: style a parent based on its children, the long-awaited companion to attribute matching.
- :is() and :where() for grouping selectors: collapse repetitive selector lists, and note how :where() zeroes specificity.
- Pull attribute values into content with attr(): read an attribute in CSS the way these selectors match against one.
Sources
Authoritative references this article was fact-checked against.
- Attribute selectors - CSS, MDN Web Docsdeveloper.mozilla.org
- Selectors Level 4, case-sensitivity, W3Cw3.org
- Case-insensitive CSS attribute selectors, Can I usecaniuse.com
- Attribute selector case-sensitive modifier (s), Can I usecaniuse.com





