Topic 1.3
Attribute Selectors
In one line
Attribute selectors match elements by their attributes: whether an attribute exists, equals a value, or starts with, ends with or contains some text, optionally ignoring upper and lower case.
Think of it like this
Sorting post at a post office. You can pick out "every parcel with a fragile sticker" (the attribute exists), "every parcel addressed exactly to Pune", "every postcode starting with 41", "every address ending in India", or "every address containing MG Road". Each is a rule about a label on the parcel, not the parcel's shape.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Attribute
- Extra information in an opening tag, like
href="/css"ortype="email". - Attribute selector
- A selector in square brackets that matches elements by their attributes, like
[type="email"]. - Value
- The text after the
=in an attribute, likeemailintype="email". - Prefix / suffix
- The start of a piece of text / the end of it.
^=checks the prefix,$=the suffix. - Substring
- Any piece of text inside a longer text.
*=checks for a substring. - Case-sensitive
- Treating upper-case and lower-case letters as different, so
PDFis notpdf. - data attribute
- A custom attribute you invent, starting with
data-, likedata-status="done". - ARIA attribute
- An attribute starting with
aria-that tells assistive technology about an element's state, likearia-expanded="true".
Step by step
01Match by presence
[attr] only asks: is the attribute there? required has no value at all, so presence is the only thing to test.
input[required] {
border-left: 4px solid crimson;
}02Match an exact value
[attr="value"] must equal the whole value. input[type="email"] does not match type="email-ish".
input[type="email"] {
background: #eff6ff;
}03Match the start, end or middle
Links are the classic case: decide how a link looks from where it goes.
^= for the start, $= for the end, *= for anywhere inside.
a[href^="https://"] { color: seagreen; } /* absolute secure links */
a[href$=".pdf"] { font-weight: 700; } /* PDF files */
a[href*="hectal"] { text-decoration: underline wavy; }04Ignore upper and lower case
File names come in any case: REPORT.PDF, notes.pdf. Add i before the ] and both match.
a[href$=".pdf" i] {
font-weight: 700;
}05Style state from the HTML
When JavaScript toggles a menu, it should also set aria-expanded. Style that attribute and the visual state can never disagree with what a screen reader announces.
button[aria-expanded="true"] {
background: #1e3a8a;
color: white;
}
[data-status="done"] {
text-decoration: line-through;
}Try it yourself
- 1
Predict the upper-case PDF
In the first example, remove both
iflags. Predict which link loses its bold and(PDF)label. Run and check: the REPORT.PDF link. - 2
Add a rule for relative links
In the first example, add
a[href^="/"] { color: rebeccapurple; }. Which link turns purple? Only the one starting with/. - 3
Change the data
In the third example, change the last task's
data-statustodone. Predict the result before running: it gets crossed out, with no CSS change.
Code & diagrams
The `::after` part adds text after each PDF link; you'll learn pseudo-elements in Topic 1.6. Remove the ` i` and the upper-case report loses its bold and label.
Preview
Console
Nothing logged yet.`~=` matches whole words in a space-separated list; `*=` matches any piece of text, so it also finds `notebook`.
Preview
Console
Nothing logged yet.Expected console output
a[href$=".pdf"] 1
a[href$=".pdf" i] 2
[class~="note"] 1
[class*="note"] 2
p[lang|="en"] 1
input[required] 1
[placeholder] 2Click the button: JavaScript only flips `aria-expanded`, and the CSS restyles it. Screen readers announce "expanded" or "collapsed" from the same attribute.
Preview
Console
Nothing logged yet.Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget the quotes around a value with a dot
In the first example, change a[href$=".pdf" i] to a[href$=.pdf i] (no quotes).
Break #2
Using `*=` where you meant a whole word
Style notes with [class*="note"] { background: yellow; }.
Myth vs fact
Myth
Attribute selectors are slow and should be avoided.
Fact
Modern engines match them efficiently. Substring forms like *= on a broad key selector cost a little more, but in normal pages the difference is not measurable.
Myth
[class="card"] is the same as .card.
Fact
[class="card"] needs the whole attribute to be exactly card, so class="card featured" does not match. .card matches the word anywhere in the list.
Myth
Attribute selectors only work on standard attributes.
Fact
They match any attribute, including your own data-* attributes and aria-* attributes.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
In HTML documents, the spec lists certain attributes whose values are matched case-insensitively by default, such as
type,lang,dirandmethod, soinput[type="EMAIL"]still matches. Other attributes likehref,classanddata-*are case-sensitive. Thesflag forces case-sensitive matching, but only Firefox supports it so far. - ▸
Selectors match attributes, not live properties.
input[value="x"]checks thevalueattribute written in the HTML, not what the user typed, so it doesn't update as they type. Use:placeholder-shown,:validor JavaScript for live values. - ▸
A selector like
[aria-current="page"]is a strong pattern for design systems: the accessible state is the single source of truth, and tests can query the same attribute. - ▸
Attribute selectors with substring matches have been abused in privacy attacks that leak input values through CSS background requests. Browsers defend against this partly by matching the attribute (not the typed value) and by blocking third-party CSS through a Content Security Policy (
style-src), which you'll meet in the HTML course's head and security topics.
Remember this
- 1
An attribute is extra information written inside an opening tag, like
hrefin<a href="/css">ortypein<input type="email">. It has a name (type) and usually a value (email). An attribute selector is written in square brackets and matches elements by their attributes. - 2
[attr]matches elements that have the attribute, whatever its value (even empty):input[required]matches every required input,[disabled]every element with adisabledattribute.[attr="value"]matches when the value is exactly that text:input[type="email"]. - 3
Three selectors match part of a value.
[attr^="x"]means the value starts with x (a[href^="https://"]: absolute secure links).[attr$="x"]means it ends with x (a[href$=".pdf"]: links to PDF files).[attr*="x"]means it contains x anywhere (a[href*="youtube"]). An easy way to remember:^points up to the start,$is the end (as in regular expressions),*is "anywhere". - 4
Two older forms work on lists.
[attr~="word"]matches when the value is a space-separated list containing that whole word:[class~="note"]is the same as.noteand does not matchclass="notebook", while[class*="note"]does.[attr|="en"]matches a value that is exactlyenor starts withen-, made for language codes likelang="en-GB". - 5
Values are compared case-sensitively by default (with an exception for some HTML attributes, see Pro). Add an
ibefore the closing bracket to ignore case:a[href$=".pdf" i]matchesreport.PDFandnotes.pdf. Quotes around the value are optional for simple words ([type=email]) but required when it contains spaces, dots, slashes or starts with a digit; always quoting is the safe habit. - 6
Attribute selectors are perfect for styling state that already lives in the HTML:
[aria-expanded="true"],[aria-current="page"],[data-status="done"]. Styling the accessibility attribute instead of an extra class keeps what screen readers hear and what eyes see in sync. Each attribute selector adds the same specificity as one class.
Explain it without notes
List the attribute selector operators and what each one matches.
What is the difference between [class~="note"], [class*="note"] and [class="note"]?
Why is it a good idea to style [aria-expanded="true"] instead of an .is-open class?
How do you match a link to a PDF whatever the case of the file extension?
Practice
Give every external link (starting with http) a small arrow after it, and every mailto: link an italic style.
Style every input that has a placeholder with a grey border, and every input with type="password" with letter spacing of 2px.
Trade-offs
- ↔
Attribute selectors avoid extra classes and keep CSS tied to real meaning (link targets, input types, ARIA state), but they couple your styles to attribute values that other people or tools may change.
- ↔
Substring matches (
*=,^=,$=) are handy for content you don't control (CMS links), but can match more than intended; prefer exact values or classes in your own components.
Done when you can
I can match elements by attribute presence and exact value.
I can use
^=,$=and*=and explain each.I know the difference between
~=and*=.I can make a match case-insensitive with
i.I can style ARIA and
data-*state with attribute selectors.