Attribute Selectors
Prerequisites: Basic selectors
Attribute selectors match elements by their attributes:
| Selector | Matches when the attribute... |
|---|---|
[disabled] |
exists |
[type="email"] |
equals email exactly |
[class~="card"] |
is a space-separated list containing card |
[lang|="en"] |
is en or starts with en- |
[href^="https:"] |
starts with https: |
[href$=".pdf"] |
ends with .pdf |
[href*="example"] |
contains example |
[type="email" i] |
equals, case-insensitively |
input[type="email"],
input[type="password"] {
border: 2px solid #888;
}
a[href$=".pdf"]::after {
content: " (PDF)";
}
a[href^="http"]:not([href*="cornerbakery.example"]) {
text-decoration-style: dashed;
}
[aria-expanded="true"] {
background: #eef;
}
[data-state="sold-out"] {
opacity: 0.5;
}
::afterwithcontentadds text after an element — covered with pseudo-elements in Level 2.- Styling by ARIA and
data-*attributes keeps visual state tied to the real state in the HTML — when JavaScript flipsaria-expanded, the style follows automatically.
Try it
<style>
a { color: black; }
a[href^="https:"] { color: green; }
a[href$=".pdf"] { color: red; }
</style>
<a id="a1" href="/about">About</a>
<a id="a2" href="https://example.com">External</a>
<a id="a3" href="https://example.com/menu.pdf">Menu</a>
<script>
for (const id of ["a1", "a2", "a3"]) {
console.log(id, getComputedStyle(document.getElementById(id)).color);
}
</script>
a1 rgb(0, 0, 0)
a2 rgb(0, 128, 0)
a3 rgb(255, 0, 0)
The third link matches both attribute rules. They're equally specific, so the one that comes later wins — red. That's the cascade, next.