Skip to main content
April 1, 2026Dan Rodney/12 min read

Relational Selectors

Master CSS Relational Selectors for Advanced Styling

CSS Relational Selectors Overview

Adjacent Selectors

Target elements that immediately follow another specific element as siblings. Uses the + combinator to create precise relationships.

Child Selectors

Select direct children using > combinator, avoiding unwanted targeting of nested descendants throughout the document structure.

Pseudo Selectors

Leverage first-child, last-child, nth-child, and type-based selectors to target elements based on their position or type.

Topics Covered in This HTML & CSS Tutorial:

Adjacent Selectors, Using First-child & Last-child, Using First-of-type, Using Nth-child, Direct Child/descendant Selectors

Exercise Preview

preview relational selectors

Exercise Overview

In this exercise, you'll master CSS relational selectors—powerful tools that enable precise element targeting based on structural relationships within your HTML. These selectors are essential for creating maintainable, scalable stylesheets that adapt to content changes without requiring additional classes or IDs. By understanding how elements relate to their siblings, parents, and children, you'll write more efficient CSS that reduces markup bloat and improves code organization.

Exercise Focus

This exercise demonstrates targeting elements based on relationships between elements, eliminating the need for additional class names while maintaining precise control.

Getting Started

  1. We'll be switching to a new folder of prepared files for this exercise. In your code editor, close all open files to avoid confusion and maintain a clean workspace.
  2. For this exercise we'll be working with the Tahoe Relational Selectors folder located in Desktop > Class Files > Advanced HTML CSS Class. Open that folder in your code editor if it supports folder-based workflows (like Visual Studio Code, Sublime Text, or modern editors do).
  3. Open index.html from the Tahoe Relational Selectors folder.
  4. Preview index.html in Chrome (we'll be using its DevTools extensively for inspection and debugging).

    While this page is similar to the one you've worked on in previous exercises, there are some minor but important differences in the HTML structure. A significant amount of base styling has already been applied, but we're going to add sophisticated finishing touches using relational selectors that would be difficult to achieve with traditional class-based approaches.

  5. Leave the page open in Chrome—we'll be switching back and forth frequently to see our changes in real-time.

Setup Requirements

0/3

Adjacent Selectors

Adjacent selectors are among the most practical relational selectors, allowing you to style elements based on their immediate siblings. This is particularly useful for typographic relationships, like styling the first paragraph after a heading differently from subsequent paragraphs.

  1. Return to index.html in your code editor.
  2. In the first section tag, find the first paragraph below the h2:

    <p>Hundreds of miles of scenic hiking trails.</p>

    Let's make this first paragraph after each section's h2 stand out from the others using an adjacent selector. This selector targets a specific element only when it immediately follows another specific element. The elements must be siblings (at the same nesting level), which is why this selector is sometimes called an adjacent sibling selector. The adjacent selector uses the + combinator and is perfect for scenarios where you want to style introductory paragraphs, subheadings, or any content that logically follows another element.

  3. Open main.css from the css folder (in the Tahoe Relational Selectors folder).
  4. Below the h2 rule (which is near the top of the file), add this new rule:

    h2 + p {
       font-weight: 700;
       font-style: italic;
       opacity:.4;
       margin-top: 3px;
    }

    The h2 + p selector targets any paragraph that comes directly after an h2 element. When working with CSS selectors, especially adjacent selectors, it's often easier to read them right to left: "select paragraphs that are adjacent to (immediately follow) h2 elements." This approach creates a visual hierarchy without requiring additional classes in your HTML.

  5. Save the file.
  6. Return to Chrome, reload the page, and observe the changes:

    • In each of the 3 columns, the paragraph directly below the heading should now be italic, bold, muted (gray), and positioned closer to the heading above. This creates a clear typographic hierarchy that guides the reader's eye and establishes the relationship between headlines and their introductory content.
    • At the bottom of each column are links. Notice that it's currently difficult to distinguish between the two different links, so let's make the first link (Read More) stand out more prominently.
Reading Adjacent Selectors

With CSS selectors, especially adjacent selectors, it may be easier to read them right to left. The h2 + p selector targets paragraphs that come directly after h2 elements.

Implementing Adjacent Selectors

1

Locate Target Element

Find the first paragraph below each section's h2 element in the HTML structure

2

Create Adjacent Rule

Add h2 + p selector with font-weight: 700, font-style: italic, opacity: 0.4, and margin-top: 3px

3

Test Results

Verify that paragraphs immediately following h2 elements are now italic, bold, gray, and closer to headings

Using First-child & Last-child

The :first-child and :last-child pseudo-selectors are invaluable for styling elements based on their position within their parent container. These selectors help you create polished layouts by handling edge cases—like removing margins from the first or last elements to prevent unwanted spacing.

  1. Return to your code editor.
  2. Switch to index.html.
  3. At the bottom of any of the three section tags, notice the last paragraph has a links class and contains the two links we just observed in the browser.
  4. Switch to main.css.
  5. Below the section.text-wrapper rule, add the following new rule:

    section.links a:first-child {
       color: #05b333;
       margin-right: 15px;
    }
  6. Save the file, reload the page in Chrome, and examine the results:

    • Notice the Read More links are now green and have additional spacing to their right, creating better visual separation and establishing a clear hierarchy between the primary and secondary actions.
    • Notice there's excessive white space at the bottom of the 3 columns (more than on the sides). This occurs because the links are contained within a paragraph element that has default bottom margin—a common issue when working with semantic HTML.
  7. Return to your code editor.
  8. Below the section.text-wrapper rule, add the following new rule:

    section.text-wrapper :last-child {
       margin-bottom: 0;
    }

    NOTE: A few steps ago we attached :first-child to the link <a> tag (a:first-child) to find links that were a first child of their container.

    The selector we're writing in this step is intentionally not attached to any specific tag. Using :last-child by itself means it will target any element that is the last child of .text-wrapper, regardless of the element type. In CSS terminology, this acts like a universal selector wildcard, providing flexibility when you don't know exactly which element type might be the last child.

  9. Save the file and reload the page in Chrome. This removes the extra space at the bottom of the 3 columns, creating more balanced spacing around the text content.
  10. In Chrome, at the bottom of the first column find the Download a Trail Guide link.
  11. CTRL–click (Mac) or Right–click (Windows) on the Download a Trail Guide link and choose Inspect.
  12. In the DevTools Styles panel you should see the section.text-wrapper :last-child rule is also applying to this element—an unintended consequence!

    This occurs because the link is also a last child within its own container (the .links paragraph, which is nested inside .text-wrapper). This demonstrates a critical principle: relational selectors can have cascading effects through nested elements. We must be extremely careful with selectors like :last-child to avoid unintended styling applications.

    There are multiple approaches to solving this specificity issue. We could target .links directly, but what if some columns don't have links? We still want to use a relational selector to remove extra spacing from any element that might be at the bottom of a column. The solution lies in being more specific about which children we want to target.

  13. In the DevTools Elements panel (where you see the HTML structure), notice that the links paragraph is nested inside a text-wrapper div. We need to target only immediate children of the text-wrapper div, not nested descendants.

First-child vs Last-child Selectors

FeatureFirst-childLast-child
PurposeTarget first child elementTarget last child element
Syntax Examplea:first-child:last-child
SpecificityCan attach to specific elementsCan work as wildcard selector
Recommended: Use with caution - last-child can apply to unintended elements when used as wildcard

Direct Child/Descendant Selectors

The direct descendant selector (child combinator) is crucial for controlling CSS specificity and avoiding unintended style inheritance. While the descendant selector (space) targets all nested elements, the direct child selector (>) targets only immediate children, giving you precise control over your styling scope.

  1. Return to your code editor.
  2. In the rule you just wrote, add the > character as shown below:

    section.text-wrapper > :last-child {
       margin-bottom: 0;
    }

    NOTE: Think of the > as a pointing arrow indicating direct parentage, rather than a "greater than" symbol. This combinator ensures we only target elements that are direct children, not grandchildren or deeper descendants.

  3. Save the file and reload the page in Chrome. In the Styles panel you should see that the Download a Trail Guide link no longer has the :last-child rule applied, demonstrating the precision of the direct child selector.

Selector Precision Warning

We have to be very careful with selectors such as last-child! They can apply to unintended elements including grandchildren and great-grandchildren.

Direct vs Descendant Selection

Direct Child (>)

Targets only immediate children, preventing selection of nested descendants. Think of > as a pointing arrow rather than greater than symbol.

General Descendant (space)

Selects all descendants including children, grandchildren, and deeper nested elements throughout the document tree.

Using First-of-type

The :first-of-type selector offers more flexibility than :first-child because it considers element types rather than just position. This makes it particularly valuable when your HTML structure might vary or when you want to target the first instance of a specific element type within a container.

  1. Still in Chrome, CTRL–click (Mac) or Right–click (Windows) on any of the column headings (such as Take a Hike) and choose Inspect.
  2. Press your Delete key to remove it from the DOM temporarily.
  3. Notice the first paragraph (which previously was positioned just below the heading) loses its bold italic styling because it's no longer adjacent to an h2 element.
  4. Reload the page to restore the heading.
  5. Return to your code editor.
  6. Find the h2 + p rule.

    While this rule currently functions correctly, it creates a brittle dependency on the presence of headings. What if a content management system allows editors to remove headings, or if the layout changes? There's a more robust approach: target the first paragraph in each section, regardless of what precedes it.

  7. Change the h2 + p selector name to section p:first-of-type as shown below:

    section.text-wrapper p:first-of-type {
       font-weight: 700;
       font-style: italic;
       opacity:.4;
       margin-top: 3px;
    }
  8. Save the file, and reload the page in Chrome. The first paragraphs should maintain their bold italic formatting.
  9. Still in Chrome, CTRL–click (Mac) or Right–click (Windows) on any of the column headings (such as Take a Hike) and choose Inspect.
  10. Press your Delete key to remove it.
  11. Notice that this time, the first paragraph (which previously was below the heading) retains its bold italic styling because it's still the first paragraph of its type within the section.

    This demonstrates an important principle in CSS architecture: there are multiple ways to target elements, and the best approach depends on your content strategy and structural requirements. While relational selectors provide powerful targeting capabilities, they create dependencies on HTML structure. If you can't guarantee those relationships will remain stable, consider using semantic classes instead.

  12. Reload the page to restore the original layout.

Adjacent vs First-of-type Selectors

Pros
First-of-type works even when preceding elements are removed
More resilient to HTML structure changes
Targets based on element type rather than position
Cons
Adjacent selectors provide more specific element relationships
First-of-type may select unintended elements of same type
Less precise than adjacent sibling selectors
Selector Flexibility

As you can see, there are many ways to target something with CSS. While relational selectors can be useful, they require that those relationships will not change. If you can't be sure about the relationship, consider using a class instead.

Using Last-of-type

Navigation elements often require special treatment for the last item to prevent unwanted spacing or to create visual balance. The :last-of-type selector is perfect for these edge-case scenarios where the final element needs different styling.

  1. Return to your code editor and examine the navigation styling. All the links in the nav currently have right margin, which creates slightly more space to the right of the navigation compared to the left side of the logo, disrupting visual balance.
  2. In the min-width: 600px media query, below the nav li rule add the following new rule:

    nav li:last-of-type {
       margin-right: 0;
    }
  3. Save the file, and reload the page in Chrome. The spacing to the right of the navigation should now be reduced, creating better symmetry with the space to the left of the logo and improving the overall visual balance of the header.

Using Nth-child

The :nth-child() selector is perhaps the most versatile of the relational selectors, allowing you to target elements based on their numerical position or mathematical patterns. This selector is invaluable for creating alternating row styles, targeting specific columns in layouts, or implementing complex design patterns without additional markup.

  1. Return to your code editor.
  2. Let's switch to another page in this site to explore :nth-child functionality. In your code editor open hikes.html from the Tahoe Relational Selectors folder.
  3. Find the main tag and notice this page has a price-list class, which allows us to create page-specific styles without affecting the other page—a good practice for maintaining CSS organization in larger projects.
  4. Preview hikes.html in Chrome.

    Consider this design challenge: if we wanted to style the second column specifically, how could we target it? It's not the first or last-child, nor the first or last-of-type. The nth-child selector allows us to specify the exact numerical position of the child we want to target, making it possible to create sophisticated layout variations.

  5. Return to main.css in your code editor.
  6. Before we create the final styling, let's perform a test to understand how nth-child works. In the min-width: 950px media query, below the section:not(:last-child) rule add the following test rule:

    main.price-list :nth-child(2) {
       background: red;
       border: 5px solid black;
    }

    Within the parentheses of nth-child() we specify the numerical position of the child we want to target. We're using a bright background color and border so we can easily visualize which elements have been selected by our selector.

  7. Save the file and reload the page in Chrome. You'll notice multiple red boxes appear throughout the page! This happens because the second child of every element within the main section receives the red background and black border.

    This demonstrates a crucial principle: nth-child selectors can apply to nested elements at multiple levels of the DOM tree. Just as we saw with :last-child, if we're not specific enough, nth-child can apply to children, grandchildren, and deeper descendants. This is why combining nth-child with direct descendant selectors (>) is often considered a best practice for maintaining precise control.

  8. Return to main.css in your code editor.
  9. Let's target only the second element that is a direct descendant (immediate child) of the main price-list container. Add > to the selector as follows:

    main.price-list > :nth-child(2) {
       background: red;
       border: 5px solid black;
    }
  10. Save the file and reload the page in Chrome. Now only the second column displays the red background with black border, demonstrating the precision of the direct child combinator.
  11. Return to main.css in your code editor.
  12. Let's explore one of nth-child's most powerful features: its ability to target patterns using keywords and mathematical expressions. Change the 2 to odd as shown below:

    main.price-list > :nth-child(odd) {
       background: red;
       border: 5px solid black;
    }
  13. Save the file and reload the page in Chrome. Now the first and third columns display the red background with black border. The nth-child selector also accepts even, mathematical expressions like 2n+1, or any positive integer, making it incredibly flexible for creating patterns.
  14. Return to main.css in your code editor.
  15. Now we can implement the actual design intent. Remove the test styling and add appropriate background color and spacing to create a professional alternating column layout:

    main.price-list > :nth-child(odd) {
       background: #ddd;
       margin-top: 78px;
    }
  16. Save the file and reload the page in Chrome. The first and third columns should now have a subtle light gray background and additional top spacing, creating a staggered visual effect that draws attention to the middle column while maintaining excellent readability and visual hierarchy. This technique is commonly used in pricing tables and feature comparisons to highlight preferred or premium options.

Nth-child Selector Caution

We have to be careful with the nth-child selector. If we're not specific enough, nth-child can apply to children, grand-children, etc. It's often best practice to combine it with direct descendant selector.

Nth-child Selector Options

Specific Number

Use nth-child(2) to target the second child element specifically. Requires exact positioning knowledge within parent container.

Odd/Even Pattern

Use nth-child(odd) or nth-child(even) to target alternating elements. Perfect for zebra striping and pattern-based styling.

Direct Descendant

Combine with > operator like main > :nth-child(odd) to target only immediate children and avoid unwanted selections.

Key Takeaways

1Adjacent selectors (h2 + p) target elements that immediately follow other specific elements as siblings, eliminating need for additional class names
2First-child and last-child selectors can act as wildcards when not attached to specific elements, potentially applying to unintended descendants
3Direct child selectors using > combinator target only immediate children, preventing unwanted selection of nested descendants throughout document
4First-of-type selectors are more resilient to HTML structure changes than adjacent selectors, working even when preceding elements are removed
5Nth-child selector requires careful specificity - combine with direct descendant operator to avoid applying styles to grandchildren and deeper nested elements
6Nth-child accepts specific numbers, odd/even keywords, and mathematical expressions for flexible element targeting based on position
7Relational selectors require stable HTML relationships - consider using classes instead when element relationships might change
8CSS selector specificity matters - read complex selectors right-to-left for better understanding of targeting logic

RELATED ARTICLES