Command Palette

Search for a command to run...

Hectal
PHASE 1Beginner ~19 min· topic 3 of 6

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" or type="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, like email in type="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 PDF is not pdf.
data attribute
A custom attribute you invent, starting with data-, like data-status="done".
ARIA attribute
An attribute starting with aria- that tells assistive technology about an element's state, like aria-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.

style.csswhole filecss
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".

style.csswhole filecss
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.

style.csswhole filecss
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.

style.csswhole filecss
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.

style.csswhole filecss
button[aria-expanded="true"] {
  background: #1e3a8a;
  color: white;
}
[data-status="done"] {
  text-decoration: line-through;
}
Style state from the HTMLdiagram
Rendering diagram…

Try it yourself

  1. 1

    Predict the upper-case PDF

    In the first example, remove both i flags. Predict which link loses its bold and (PDF) label. Run and check: the REPORT.PDF link.

  2. 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. 3

    Change the data

    In the third example, change the last task's data-status to done. Predict the result before running: it gets crossed out, with no CSS change.

Code & diagrams

Links styled by where they go New tab

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

Press Run to see the page.

Console

Nothing logged yet.
Testing attribute selectors from JavaScript New tab

`~=` matches whole words in a space-separated list; `*=` matches any piece of text, so it also finds `notebook`.

Preview

Press Run to see the page.

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] 2
Styling state with ARIA and data attributes New tab

Click the button: JavaScript only flips `aria-expanded`, and the CSS restyles it. Screen readers announce "expanded" or "collapsed" from the same attribute.

Preview

Press Run to see the page.

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).

terminal
$ the preview shows
── what you'll see ──
Neither PDF link is bold and the (PDF) labels disappear.

Break #2

Using `*=` where you meant a whole word

Style notes with [class*="note"] { background: yellow; }.

terminal
$ the preview shows
── what you'll see ──
Elements with classes `notebook`, `footnote` and `keynote` turn yellow too.

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, dir and method, so input[type="EMAIL"] still matches. Other attributes like href, class and data-* are case-sensitive. The s flag forces case-sensitive matching, but only Firefox supports it so far.

  • ▸

    Selectors match attributes, not live properties. input[value="x"] checks the value attribute written in the HTML, not what the user typed, so it doesn't update as they type. Use :placeholder-shown, :valid or 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. 1

    An attribute is extra information written inside an opening tag, like href in <a href="/css"> or type in <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. 2

    [attr] matches elements that have the attribute, whatever its value (even empty): input[required] matches every required input, [disabled] every element with a disabled attribute. [attr="value"] matches when the value is exactly that text: input[type="email"].

  3. 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. 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 .note and does not match class="notebook", while [class*="note"] does. [attr|="en"] matches a value that is exactly en or starts with en-, made for language codes like lang="en-GB".

  5. 5

    Values are compared case-sensitively by default (with an exception for some HTML attributes, see Pro). Add an i before the closing bracket to ignore case: a[href$=".pdf" i] matches report.PDF and notes.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. 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

01

List the attribute selector operators and what each one matches.

02

What is the difference between [class~="note"], [class*="note"] and [class="note"]?

03

Why is it a good idea to style [aria-expanded="true"] instead of an .is-open class?

04

How do you match a link to a PDF whatever the case of the file extension?

Practice

01

Give every external link (starting with http) a small arrow after it, and every mailto: link an italic style.

02

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.