Start Consultation

(+62)81 471 5682

Writing a style rule is only half the job. The other half is telling the browser which elements the rule applies to, and that is what CSS selectors do. A selector is the pattern at the start of a rule that picks out one element, a group of elements or a part of an element.

This guide walks through the selector types you will use most, then explains specificity, the system that decides which rule wins when two disagree.

The Four Basic CSS Selectors

p { line-height: 1.6; }           /* element selector */
.card { padding: 16px; }          /* class selector */
#site-header { height: 80px; }    /* id selector */
* { margin: 0; }                  /* universal selector */

Grouping Selectors

When several selectors need the same styles, separate them with commas and write the rule once.

h1, h2, h3 {
  font-family: Georgia, serif;
}

You can also join selectors with no space to make a stricter match. For example, p.intro matches only paragraphs that have the class intro.

Combinators: Selecting by Relationship

Combinators match elements based on where they sit in relation to other elements.

nav a { color: white; }       /* descendant */
ul > li { margin: 4px; }      /* child */
h2 + p { font-size: 18px; }   /* adjacent sibling */
h2 ~ p { color: #444444; }    /* general sibling */

Attribute Selectors

Square brackets select elements by their attributes.

img[alt] { border: 1px solid green; }
input[type="email"] { border-color: blue; }
a[href^="https"] { color: green; }
a[href$=".pdf"] { font-weight: bold; }

The first rule matches any image that has an alt attribute. The second matches an exact value. The symbols ^=, $= and *= match a value that starts with, ends with or contains the given text.

Pseudo-Classes

A pseudo-class selects an element in a particular state or position. It is written with one colon.

a:hover { text-decoration: underline; }
input:focus { border-color: blue; }
li:first-child { font-weight: bold; }
tr:nth-child(even) { background-color: #f2f2f2; }
.btn:not(.primary) { background-color: white; }

Pseudo-Elements

A pseudo-element styles a part of an element, not the whole thing. It is written with two colons.

.note::before { content: "Note: "; font-weight: bold; }
.price::after { content: " USD"; }
p::first-line { text-transform: uppercase; }

::before and ::after insert generated content at the start or end of an element. They only appear when the content property is set. ::first-line styles the first line of a block of text, even when the line wraps differently.

How Specificity Decides the Winner

When two rules set the same property on the same element, the browser picks the one with the more specific selector. A simple way to compare them is to add up points.

Selector partPointsExamples
Inline style attribute1000style="color: red"
Id100#site-header
Class, attribute, pseudo-class10.card, [type="email"], :hover
Element, pseudo-element1p, ::before
Universal selector0*

A few worked scores:

The highest score wins. If two selectors tie, the rule written later in the stylesheet wins.

Two notes keep this accurate. First, the points are a memory aid. The browser really compares the columns from left to right, so no number of classes can outrank a single id. Second, :not() adds nothing itself, but the selector inside it does count.

Tips for Writing Better Selectors


How the Browser Reads a Selector

It helps to know that a browser reads a selector from right to left. Take this rule:

nav ul a { color: white; }

The browser first finds every a element on the page. For each one, it then checks whether there is a ul somewhere above it, and a nav above that. The last part of the selector, here the a, is the element that actually receives the styles. Everything to its left is only a condition.

This gives you a simple way to read any of the CSS selectors you meet in other people’s code: look at the right-hand end first to see what is being styled, then read leftward to see when.

More Pseudo-Classes Worth Knowing

The earlier list covers the pseudo-classes you will use daily. A few more appear often enough to learn early.

li:last-child { border-bottom: none; }
p:nth-of-type(2) { font-weight: bold; }
input:checked + label { color: green; }
button:disabled { opacity: 0.5; }
a:focus-visible { outline: 2px solid blue; }

The difference between :nth-child and :nth-of-type causes regular confusion. Suppose a section contains a heading followed by three paragraphs. p:nth-child(2) matches the first paragraph, because it is the second child of the section. p:nth-of-type(2) matches the second paragraph, because it only counts paragraphs.

Links have their own set: :link for unvisited links, :visited, :hover and :active. When you style all four, write them in that order. Each one has the same specificity, so a later rule overrides an earlier one, and this order keeps the hover and active styles working.

More Pseudo-Elements

Three more pseudo-elements are easy to use and handy on real pages.

input::placeholder { color: #888888; }
::selection { background-color: #ffe08a; }
li::marker { color: #1a73e8; }

Each of these accepts only a limited set of properties. ::marker, for example, allows color and font settings but not padding or a background. If a declaration seems to be ignored on a pseudo-element, that limit is usually the reason.

Grouping with :is(), :where() and :has()

Three newer pseudo-classes make long selectors shorter and solve problems that used to need extra classes.

:is(h1, h2, h3) a { text-decoration: none; }
:where(ul, ol) { padding-left: 20px; }
.card:has(img) { padding: 0; }

:is() takes a list and matches any item in it. The first rule is the same as writing h1 a, h2 a, h3 a. Its specificity is that of the most specific item in the list.

:where() works the same way, but it always adds zero specificity. That makes it ideal for default styles, because any later rule can override them without a fight.

:has() selects an element based on what it contains. The third rule matches only the cards that have an image inside them. For many years CSS could not select a parent from its child, and :has() fills that gap. It is supported in the current versions of all major browsers.

Attribute Selectors in Real Projects

Attribute selectors are useful whenever the HTML already says what you need, so you do not have to add a class.

a[target="_blank"] { padding-right: 16px; }
input[type="checkbox"] { width: 20px; }
[lang="fr"] { font-style: italic; }
[data-state="open"] { display: block; }
a[href$=".pdf" i] { font-weight: bold; }

The fourth rule shows a common pattern. JavaScript changes a data attribute on an element, and CSS reacts to the new value. The script never has to touch the styles directly.

The i before the closing bracket in the last rule makes the match ignore upper and lower case, so the rule also matches a link that ends in .PDF.

The Cascade: Where Specificity Fits In

Specificity is one step in a longer decision process called the cascade. When several declarations set the same property on the same element, the browser settles it in this order:

  1. Declarations marked !important win over normal ones.
  2. Inline styles, written in the style attribute, win over rules in a stylesheet.
  3. The selector with the higher specificity wins.
  4. If everything is still equal, the rule that comes last wins.

Most everyday conflicts are decided at step 3 or step 4, which is why specificity and source order deserve the most attention.

Inheritance is a separate idea that is often mixed up with the cascade. Some properties, such as color, font-family and line-height, pass from a parent to its children. Others, such as margin, padding and border, do not. An inherited value is the weakest of all. Any rule that targets the element directly beats it, even a rule that uses the universal selector.

More Worked Specificity Scores

Practice with a few longer CSS selectors makes the scoring automatic.

Combinators such as the space, the child arrow and the plus sign add no points. Only the parts they connect are counted.

Naming Classes So Selectors Stay Simple

Good class selectors start with good class names. A few habits keep them readable:

Many teams follow a naming convention called BEM, which writes names in the pattern block__element–modifier. You do not need it to begin with, but you will meet it in existing projects.

Common Mistakes with CSS Selectors

A space that changes the meaning. p.intro matches a paragraph with the class intro. p .intro matches any element with that class inside a paragraph.

A missing dot or hash. card { } looks for an element called card, which does not exist. The class version is .card.

A comma that drops the first part. .card h2, p styles every paragraph on the page, not only those inside a card. Each item in a list is a complete selector, so write .card h2, .card p.

Capitals that do not match. class=”Menu” in the HTML is not matched by .menu in the CSS.

The same id used twice. The styles may still apply, but links to that id and scripts that look for it will find only the first one.

Raising specificity to win. Adding ids or !important fixes today’s conflict and creates tomorrow’s. Lower the other selector instead.

Testing a Selector in the Browser

You can check what a selector matches without editing any file.

Open the developer tools and press Ctrl + F (Cmd + F on a Mac) inside the HTML panel. The search box accepts selectors, so typing .card a steps through every matching element.

The console gives you a count. Type the line below and the browser returns the number of matches:

document.querySelectorAll(".card a").length

When a rule seems to be ignored, select the element and read the styles list. A declaration that lost to a stronger rule is shown crossed out, with the winning rule above it.

CSS Selectors Cheat Sheet

Frequently Asked Questions

What is the difference between a pseudo-class and a pseudo-element?

A pseudo-class selects a whole element in a certain state or position, such as :hover. A pseudo-element selects a part of an element or generated content, such as ::first-line or ::before.

Which CSS selectors should beginners use most?

Class selectors. They work on any element, can be reused across a page and keep specificity low, which prevents most override problems.

Why is my CSS rule not being applied?

The usual causes are a typo in the selector, a class name that does not match the HTML or another rule with higher specificity. The browser’s developer tools show which rule won.

Final Thoughts

CSS selectors are how you point at the exact element you want to style, and specificity is how the browser settles conflicts between rules. Build most of your styles on simple class selectors, and the rest of your CSS will stay predictable.

Related reading


Can CSS select a parent element?

Yes, with :has(). For example, .card:has(img) matches a card that contains an image, and form:has(input:invalid) matches a form with an invalid field. Before :has() arrived, this needed an extra class or JavaScript.

Are CSS selectors case sensitive?

Partly. Element names in HTML are not, so P and p match the same elements. Class names and ids are case sensitive, and attribute values usually are too, unless you add the i flag.

How many classes can one element have?

As many as you need, separated by spaces in the class attribute. To match an element that has two particular classes at once, join them with no space, as in .btn.primary.

Do long selectors make a page slower?

In practice, almost never. Modern browsers match selectors very quickly. The real reasons to keep selectors short are that they are easier to read, easier to override and less likely to break when the HTML changes.

Leave a Reply

Your email address will not be published. Required fields are marked *