[attribute]
Selects elements by an HTML attribute and its value: exact, starting, ending, a fragment, or a word from a list.
Example
#a[href^="https"]::after {
content: " ↗";
}
a[href$=".pdf"]::before {
content: "PDF";
margin-right: 6px;
padding: 1px 5px;
border-radius: 4px;
background: #f43f5e;
color: #fff;
font-size: 11px;
font-weight: 700;
}<ul class="stage" style="display: grid; gap: 8px; margin: 0; padding: 12px 14px; list-style: none;">
<li><a href="https://example.com" style="color: #c4b5fd;">External documentation</a></li>
<li><a href="/files/syllabus.pdf" style="color: #c4b5fd;">Course syllabus</a></li>
<li><a href="/courses" style="color: #c4b5fd;">Course list</a></li>
</ul>Definition and usage
#You write an attribute selector in square brackets. [title] alone selects every element that has a title attribute, regardless of its value, even an empty one. Once you add an operator and a value, you’re checking the attribute’s content: the exact value, its start, its end, any fragment, or a single word from a list.
It’s a convenient way to style elements without adding classes. You can tell form fields apart by their type (input[type="email"]), external links by the start of the address (a[href^="https"]), and files by their extension (a[href$=".pdf"]). It also works great with data-* and aria-* attributes, which describe a component’s state, for example [aria-expanded="true"].
A quoted value is always safe. You can only drop the quotes when the value is a simple word of letters, digits, hyphens, and underscores that doesn’t start with a digit. Case in the value usually matters, unless you add the i flag before the closing bracket.
An attribute selector’s specificity is (0, 1, 0), the same as a class. You’ll run into the |= operator less often: [lang|="en"] matches the value en as well as values that start with en-, such as en-US.
Syntax
#[attribute] | [attribute="v"] | [attribute^="v"] | [attribute$="v"] | [attribute*="v"] | [attribute~="v"] | [attribute|="v"] | [attribute="v" i]Notation variants
#[attribute]
Only the presence of the attribute counts, the value doesn’t matter. Only the link with atitleattribute matches.[attribute="v"]
Exactly that value, character for character. The element with "css new" doesn’t match, even though it also contains the word css.[attribute^="v"]
The value starts with the given text. This is the usual way to flag external links.[attribute$="v"]
The value ends with the given text. The fileMAP.PDFdoesn’t match, because case matters.[attribute*="v"]
The given text appears anywhere in the value, even in the middle of the address. Both links to files match.[attribute~="v"]
The value is a list of space-separated words, and one of them is exactly "css". The word "css-grid" doesn’t count.flag i
Theiflag turns off case sensitivity, so both PDF files match: lowercase and uppercase.
Notation variants in practice
#[title]
title attribute matches.[title] { background: #7c3aed; color: #fff; }[href^="https"]
[href^="https"] { background: #7c3aed; color: #fff; }[href$=".pdf"]
MAP.PDF doesn’t match, because case matters.[href$=".pdf"] { background: #7c3aed; color: #fff; }[href*="files"]
[href*="files"] { background: #7c3aed; color: #fff; }[href$=".pdf" i]
i flag turns off case sensitivity, so both PDF files match: lowercase and uppercase.[href$=".pdf" i] { background: #7c3aed; color: #fff; }More examples
#.toggle[aria-expanded="true"] {
background: #7c3aed;
}
.toggle[aria-expanded="false"] + .details {
display: none;
}<style>
.toggle { padding: 8px 14px; border: 0; border-radius: 8px; background: #3f3f46; color: #fff; font: inherit; cursor: pointer; }
.details { margin: 8px 0 0; color: #a1a1aa; }
</style>
<button class="toggle" type="button" aria-expanded="false" onclick="this.setAttribute('aria-expanded', this.getAttribute('aria-expanded') === 'true' ? 'false' : 'true')">Lesson details</button>
<p class="details">Duration: 15 minutes. Level: beginner.</p>Click the button. The script only changes the aria-expanded value; the button’s color and the visibility of the details come entirely from CSS.
Browser support
#Widely available
Works in all modern browsers, phones included. You can use it without worry.
- Chrome
- Edge
- Firefox
- Safari
Good practices
#- Keep a component’s state in attributes like
aria-expandedordata-state, and style it with an attribute selector. The look will always match what screen reader users hear. - An attribute selector only sees attributes written in the HTML.
input[value=""]won’t react to typing, and[checked]won’t react to clicking the field. Pseudo-classes like:checkedand:placeholder-shownhandle the live state. - Addresses and file extensions get written differently, so with
^=,$=, and*=it’s worth considering theiflag.
Related entries
#- .classSelects elements that have the given class in their class attribute. The most commonly used selector for styling components.
- #idSelects the element with the given identifier from its id attribute. It carries very high specificity, so its rules are hard to override later.
- SpecificityA selector’s weight, which decides which rule wins when several rules set the same property on the same element.
- :checkedMatches a checked checkbox, a selected radio button, and a selected option in a select list.
- :not()Selects elements that match neither the given selector nor any selector in the list.
Spotted a mistake or missing an example? Write to us.