CSS Selectors: How to Target HTML Elements

This article covers CSS selectors - the part of a CSS rule that decides which elements get styled. We'll also look at how the cascade decides which rule wins when two rules conflict.

If you're new to CSS, the previous article "What is CSS?" covers how rules are written, the box model, colors, units, and pseudo-classes like :hover - good to read first if you haven't already. That said, this article is designed to be readable on its own.

Basic Selectors

A CSS selector is the part of a rule that says "apply these styles to..." Here are the selectors you'll use most of the time.

Type Selector (Element)

The simplest selector. Matches every element of a given type.

Type selectors are handy for setting page-wide defaults - base font size, link color, paragraph spacing - that you'll occasionally override with more specific selectors later.

Class Selector (.)

You might be wondering, "what exactly is a class?" It's actually pretty simple. In CSS, a class is one of the most common ways to apply styles, and the same class can be attached to multiple elements.

A class is a label you put on one or more HTML elements, using the class attribute. In CSS, you target it with a dot followed by the class name, e.g. .highlight.

You simply pick any name you like (the class name) and attach it to the elements you want to style. As we'll see in the next section, an id can only be used on one element per page, but the same class name can be attached to as many elements as you want.

A rule like p { ... } applies to every <p> on the page, but often you only want to style some of them. That's where classes come in - they let you target just the elements you choose. For example, if you want your own highlight style, you can use a class to apply it to specific paragraphs only:

Tip: Class names can contain letters (a-z, A-Z), digits (0-9), hyphens (-), and underscores (_). The main thing to watch out for: don't start a class name with a digit. For example, class="my-class-1" works as expected, but class="1st-class" can't be selected with .1st-class in CSS without special escaping, so it's best avoided. Class names are also case-sensitive, so .Highlight and .highlight are treated as different classes.

Classes are the workhorse of CSS. Any element can have a class, the same class can be applied to many elements, and one element can have multiple classes (just separate them with spaces: class="highlight large").

ID Selector (#)

An ID is similar to a class, but it should only be used once per page. IDs are written with a # in CSS:

#page-title {
  color: navy;
  text-align: center;
}

We introduced IDs in the previous article as In-page links and the id attribute, but they're also used as CSS selectors. Since the same ID acts as both an in-page link target and a CSS selector, IDs are typically used to uniquely identify a single element on the page.

IDs are also commonly used as targets for JavaScript.

That said, in modern CSS, classes are usually preferred over IDs for styling. IDs have very high priority in the cascade, so they override class rules, which can make them harder to deal with later. And since IDs already serve as in-page link targets, using them for styling on top of that tends to cause more problems than it solves.

In this example, the heading stays navy rather than turning red, because the ID selector has higher specificity than the class selector. If you wanted to make it red, you'd have to either change the ID rule to use a class, or add !important to the class rule (not recommended).

Because of this, using classes is often easier to manage when you want to override styles later or combine multiple styles on the same element.

Universal Selector (*)

The asterisk matches every element on the page. It's powerful, but since the styles you write end up applying to every element, it's easy to introduce unintended side effects. Use it sparingly.

This makes every element on the page blue, with an aqua background and a border - not usually what you want! And because * also matches root elements like <html> and <body>, it can cause the layout to break in ways you didn't expect.

That said, the universal selector can be useful in certain cases. For example, it's sometimes used as a "reset" to wipe out the browser's default margins and padding:

* {
  margin: 0;
  padding: 0;
}

Some developers intentionally start from this blank slate and then build their styles back up, one piece at a time.

Grouping: One Rule for Multiple Selectors

Take this example - it defines the exact same styles for h1, h2, and h3. The repetition adds up quickly:

h1 {
  font-family: "Georgia", serif;
  color: #222;
}

h2 {
  font-family: "Georgia", serif;
  color: #222;
}

h3 {
  font-family: "Georgia", serif;
  color: #222;
}

When you find yourself writing the same declarations for multiple selectors, you can group them together by separating them with commas:

This is exactly the same as writing three separate rules with the same declarations, but much shorter.

You can also group selectors together and then override specific properties for just some of them. For example, the following rules share a font and color for all three headings, but only h1 gets a background color:

h1, h2, h3 {
  font-family: "Georgia", serif;
  color: #222;
}

/* Only h1 gets a light red background */
h1 {
  background-color: #ffcccc;
}

Descendant Selector (Space)

A space between two selectors means "match the second selector, but only when it's inside the first." This is called the descendant combinator.

The descendant doesn't have to be a direct child - it can be nested any number of levels deep. As long as it's somewhere inside, it matches.

The descendant combinator works with any selectors, not just type selectors like article p. For example, .card .highlight selects elements with the highlight class that are inside an element with the card class.

Child Selector (>)

If you want to match only direct children (one level deep, no deeper), use the > combinator. This is called the child combinator.

Compare this with the descendant selector (a space) above. The rule .card .highlight matches every element with the .highlight class anywhere inside an element with the .card class, no matter how deeply nested. On the other hand, .card > .highlight only matches elements with the .highlight class that are direct children of an element with the .card class.

Next-sibling Selector (+)

The + combinator matches an element that comes immediately after another element at the same level. This is sometimes also called the adjacent sibling selector, which is the older name from earlier CSS specifications.

This is useful for things like making the first paragraph after a heading stand out, without having to add a class to it.

Combining Selectors

All of these selectors can be combined. No space between two selectors means "match an element that satisfies both."

Tip: There are many more selector types in CSS - attribute selectors, more pseudo-classes, pseudo-elements, and so on. The ones above cover the vast majority of what you'll write day-to-day. We'll look at the more advanced ones in a later article.

The Cascade: Which Rule Wins?

CSS lets multiple rules apply to the same element at the same time. When two rules conflict (say, both try to set the color of your headings), CSS uses a set of rules called the cascade to decide which one takes precedence.

Here's a simple example. Suppose you have these two rules:

p {
  color: black;
}

p.highlight {
  color: red;
}

And this paragraph:

<p class="highlight">What color am I?</p>

Both rules match the element. The first one says "all paragraphs are black." The second says "paragraphs with class highlight are red." When they conflict, CSS picks the one with the more specific selector. p.highlight is more specific than just p, so the text comes out red.

The full priority system is called specificity. Here's the rough order, from highest to lowest:

  1. Inline styles (the style attribute on an element)
  2. ID selectors (#id)
  3. Class selectors (.class), attribute selectors, and pseudo-classes (:hover)
  4. Type selectors (p, h1) and pseudo-elements

On top of that, if two rules have exactly the same specificity, the one that appears later in the stylesheet wins.

The !important flag

CSS provides a special flag called !important that you can add to a declaration. A rule marked with !important takes precedence over rules with higher specificity, and even over inline styles.

Normally an ID selector would win over a class selector, so the heading would be navy. But because the class rule is marked !important, it overrides the ID rule, and the heading comes out red.

!important is a regular part of CSS and there are valid uses for it. Common cases include:

  • Overriding styles from a third-party library or framework when you can't change the source.
  • Utility classes that are intended to win no matter what (e.g. a .hidden class that should always hide the element).
  • User stylesheets and accessibility overrides.

That said, if a regular selector can solve the problem, it's usually easier to maintain without !important, since a rule marked !important can only be overridden by another !important rule with equal or higher specificity. Reach for it when you need it, but check first whether a more specific selector would do the same job.

Putting It All Together

Here's a small but complete example that brings together the selectors and cascade ideas covered in this article, along with the pseudo-classes from the previous article.

Notice how each selector pulls its weight here. Type selectors (body) set the page-wide defaults. Grouping (h1, h2, h3) shares one rule across all the headings. The next-sibling rule (h2 + p) makes the lead paragraph stand out without needing an extra class. Classes (.highlight, .card) act as reusable building blocks. The descendant rule (.card .highlight) shows the cascade in action, overriding the default highlight color only when it appears inside a card. And the pseudo-classes give the link its three different states.

Summary

  • Basic selectors include type (p), class (.highlight), ID (#title), and universal (*).
  • Grouping (h1, h2) applies the same rule to multiple selectors at once.
  • Combinators let you target elements based on position: descendant (space), child (>), and next-sibling (+).
  • The cascade decides which rule takes precedence when two rules conflict. More specific selectors win: inline > ID > class/pseudo-class > type.
  • When specificity is equal, the rule that appears later in the stylesheet wins.
  • The !important flag overrides specificity. It's a regular part of CSS, but if a more specific selector can do the job, that's usually easier to maintain.

Try It Yourself

Open the SyncFiddle Editor, write some HTML, and experiment with selectors. Add a class to a few elements and style them differently from the rest. Try a :hover rule on a link and see it change color when you mouse over it. Write two conflicting rules for the same element and see which one wins - then flip their order and watch the result change.

Open the SyncFiddle Editor and try it out