TechEarl

Case-Insensitive CSS Attribute Selectors with the i Flag

Add an i before the closing bracket of a CSS attribute selector and the value matches case-insensitively: [href$=".pdf" i] catches .pdf, .PDF, and .Pdf. Plus the rarer s flag that forces case-sensitive matching.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
Make a CSS attribute selector case-insensitive with the i flag so file extensions and user-entered values match regardless of letter case.

Put an i before the closing bracket of any CSS attribute selector and the value matches case-insensitively:

css
/* 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 .pdf interchangeably. a[href$=".pdf" i] catches every variant with one rule instead of three.
  • URL schemes and hosts. a[href^="mailto:" i] and a[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 between open and OPEN.

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:

javascript
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:

css
/* 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:

SelectorMatches 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

Sources

Authoritative references this article was fact-checked against.

TagsCSSattribute selectorscase-insensitiveselectors level 4i flags flag

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

The CSS :has() Parent Selector: A Complete Guide

CSS :has() is the parent selector we waited 20 years for: style an element based on what it contains. Now Baseline in every engine. Form rows, quantity queries, the previous-sibling trick, and the @supports gate for old browsers.