CSS selectors

targeting a group of elements on a webpage
// updated 2025-05-01 07:44

A CSS selector finds a specific group of related objects on a webpage, in order to give each of them the same styling:

  • e.g. Every second-level heading (<h2>) can enjoy the same font sizings or line heights (or whatever) with a concise, one-line CSS rule!

In this page, we will go over the basic structure of a CSS selector and then look at the following selector types:

  • Element
  • Class (.)
  • ID (#)
  • Attribute ([])
    • Specific value
    • Partial match (^, *, $)
  • Child (>)
  • Descendant ( )
  • Sibling (+, ~)

Structure

The abstract template for two CSS selectors and their properties:

(selector1) {
  (property1) : (value1); 
}

(selector2) {
  (property2) : (value2);
}

...

Example

For example, to make a link red with no underline:

a {
  color: red;
  text-decoration: none; 
}

Selector types

Element selectors

To update the style for a top-level element, such as an <h1> or <a> we simply use the HTML element's name as the selector, e.g.:

h1 {
  ...
}
a {
  ...
}

Class selectors

To update the style for any element that has a specific class, e.g. in HTML:

<!-- index.html -->
<html>
  
  <head>
    <link rel="stylesheet" href="style.css" />
  </head>
  
  <body>
    <h1 class="redFont">This will be red!</h1>
    <p class="redFont">This will also be red!</p>
  </body>
  
</html>

Then, in CSS, we would use the dot notation to select all items of a class:

/* style.css */

.redFont { 
  color: red;
}

With that class selector, both the h1 and p will have red font. This also means that we don't need to restrict the red font to just h1 or make all h1 elements have a red font. We will see more flexibility that comes with CSS!

ID selectors

To update the style for a unique element on the page, e.g.:

<!-- index.html -->
<html>
  
  <head>
    <link rel="stylesheet" href="style.css" />
  </head>
  
  <body>
    <section id="my-map">
      ...
    </section>
  </body>
  
</html>

We would use the hashtag notation as such:

/* style.css */

#my-map {
  height: 600px;
  width: 800px;
}

This works well when we want to restrict a certain styling to one and only one element of an HTML page!

Attribute selectors

To update the style for any element that has a specific attribute, e.g.

<!-- index.html -->
<html>
  
  <head>
    <link rel="stylesheet" href="style.css" />
  </head>
  
  <body>
    <section data-status="danger">
      ...
    </section>
    <section data-status="warning">
      ...
    </section>
  </body>
  
</html>

We would use the square bracket notation:

/* style.css */

[data-status] {
  border: 2px;
  border-style: solid;
  border-color: black;  
}

[data-status="danger"] {
  background-color: red;
}

[data-status="warning"] {
  background-color: orange;
}

Note that we could specify attribute values in our selectors to further customize each value of an HTML tag attribute.

Partial match selectors

Looking at the attribute selectors above, we can see this has many fine-grained implications:

/* style.css */

/* starts with "war" */
[data-status^="war"] { ... } 

/* contains "nin" */
[data-status*="nin"] { ... }

/* ends with "ing" */
[data-status$="ing"] { ... }

In the example above, any element with a data-status value of:

  • warning would take the styles in all three declaration blocks
  • churning would take the styles in only the last two blocks
  • wartime would take the style in only the first block

An mnemonic to remember the symbols:

  • a ^ resembles a "hat" (i.e. "heads with" or "begins with")
  • a * resembles a "star" (i.e. "starring" or "featuring")
  • a $ for "the bottom line" (i.e. "ending with")

Child selectors

Let us define the parent element and child element as such:

<div> <!-- parent -->
  <p> <!-- child -->
    ...
  </p>
</div>

Then, to target only the child element, we would simply use the "greater than" notation:

div > p {
  ...
}

This would target only the p tag within the div tag and nothing else within the div.

Descendant selectors

To target "grand-children" and "great-grand-children" elements (and so on), we would just keep chaining the "greater than" notation:

/* span as the grandchild of div */
div > p > span {
  ...
}

/* strong as the great-grandchild of div */
div > p > span > strong {
  ...
}

In order to select all descendants of an ancestor element, regardless of "generation", simply place them side by side:

main p {
  ...
}

Thus, that would target all p tags in the main HTML tag, regardless of whether p is a child, grand-child, great-grand-child, or however far nested p is from main!

Sibling selectors

Suppose that we have an element on the same level as another element, e.g.:

<header>
  <h2>...</h2>
  <p>...</p>
  <aside>...</p>
</header>

To reach an adjacent element on the same level as another element, we can use the "plus" notation:

h2 + p {
  ...
}

That CSS would then target any p tag that lies right after an h2 tag. Here, we get into some really refined and specific CSS selection! When working with CSS "in the real world", this kind of selection seldom happens but it exists for us when we need it!

To reach a non-adjacent element on the same level, we would use the "tilde" notation:

h2 ~ aside {
  ...
}

That should about cover most of the selectors a front-end developer would encounter on a daily basis! Of course, much to anyone's amazement, even more CSS selectors exist. At that point, we would simply search (or ask AI) for them when the need arises!

⬅️ older (in textbook-css)
🎨 CSS in HTML
newer (in textbook-css) ➡️
CSS colors 🎨
⬅️ older (in code)
🎨 CSS in HTML
newer (in code) ➡️
CSS colors 🎨
⬅️ older (posts)
🎨 CSS in HTML
newer (posts) ➡️
CSS colors 🎨