Question

CSS Parent Selector Explained: Selecting a Parent <li> from an <a>

csscss-selectors

Question

How can I select the <li> element that is the direct parent of an anchor element in CSS?

For example, I would like to write something like this:

li < a.active {
  property: value;
}

I understand this can be done with JavaScript, but I want to know whether there is a native CSS solution, especially in CSS Level 2.

In my case, the menu HTML is generated by a CMS, so I cannot easily move the active class from the <a> element to the <li> element unless I customize the menu generation code, which I would prefer to avoid.

Short Answer

By the end of this page, you will understand why older CSS could not select a parent element from a child, what a CSS parent selector really means, how the modern :has() pseudo-class solves this problem, and what alternatives developers used before :has() was available.

Concept

In CSS, selectors traditionally work from left to right by matching the final element, even though browsers may optimize internally. In practical terms, classic CSS was designed mainly to style an element based on:

  • its own type, class, or ID
  • its attributes
  • its ancestors
  • its siblings

What it could not do in CSS Level 2 or most of CSS Level 3 was select an element based on one of its children or descendants. That missing feature is what developers often call a parent selector.

In your example, you want to style this parent element:

<li>
  <a class="active" href="/home">Home</a>
</li>

based on the fact that it contains an <a> with class active.

Older CSS had no selector for this. So in CSS Level 2, the answer was effectively no, there is no parent selector.

Modern CSS now provides a way to do this using the :has() pseudo-class:

li:has(> a.active) {
  property: value;
}

This means:

  • select every li
  • that has a direct child a.active

This is the modern native CSS solution.

Why this matters in real programming:

  • menus often place state classes like active on links, not containers
  • form fields may need parent wrappers styled when child inputs are invalid
  • cards or components may change appearance depending on nested content
  • CMS-generated markup often cannot be easily changed

So the ability to select a parent based on child content is very useful in real-world CSS.

Mental Model

Think of classic CSS like reading a family tree only downward:

  • You can style a child because it lives inside a parent.
  • You can style a descendant because it lives inside an ancestor.
  • But you could not go backward and say, "Style the parent because one child has a special badge."

That is exactly what a parent selector would do.

A simple analogy:

  • Old CSS: "Choose the person wearing the red shirt."
  • Parent selector: "Choose the parent of the person wearing the red shirt."

Classic CSS could identify the child, but not climb back up to style the parent.

Modern :has() works like a rule that says:

  • "Select any folder that contains a file named active."

So li:has(> a.active) means:

  • select the li
  • if inside it there is a direct child anchor with the class active
Take Quiz

Syntax and Examples

Core syntax

Modern CSS uses :has() for parent-like selection.

li:has(> a.active) {
  color: red;
}

This selects an li if it has a direct child matching a.active.

Example HTML

<ul>
  <li><a href="/home">Home</a></li>
  <li><a href="/about" class="active">About</a></li>
  <li><a href="/contact">Contact</a></li>
</ul>

Example CSS

li:has(> a.active) {
  background: #eef;
  font-weight: bold;
}

The second <li> will be styled because it contains a direct child <a class="active">.

Direct child vs descendant

If the anchor is directly inside the li:

li:has(> a.active) {
  border-left: 4px solid blue;
}

If the anchor might be nested deeper:

li:has(a.active) {
  border-left: 4px solid blue;
}

The > means direct child only.

Older CSS alternative

Before :has(), CSS alone could not select the parent. Common workarounds were:

  • add the class to the parent in the HTML
  • use JavaScript to add a class to the parent
  • change CMS templates so the parent gets an active class

Example preferred markup if you control HTML:

<li class="active"><a href="/about">About</a></li>
li.active {
  background: #eef;
  font-weight: bold;
}

Step by Step Execution

Consider this HTML:

<ul>
  <li><a href="/home">Home</a></li>
  <li><a href="/about" class="active">About</a></li>
</ul>

And this CSS:

li:has(> a.active) {
  background: gold;
}

Here is what happens conceptually:

  1. CSS looks at each li element.
  2. For the first li, it checks whether it has a direct child matching a.active.
  3. The first li contains <a href="/home">Home</a>, which does not have class active.
  4. So the first li does not match.
  5. For the second li, CSS checks for a direct child matching a.active.
  6. It finds <a href="/about" class="active">About</a>.
  7. That means the second li matches the selector.
  8. The second li gets background: gold.

Trace result:

li elementContains direct child a.active?Matches?
First liNoNo
Second liYesYes

If you used this instead:

a.active {
  background: gold;
}

only the anchor itself would be styled, not the parent li. That is the key difference.

Real World Use Cases

Parent-like selectors are useful in many real projects.

Navigation menus

A CMS may mark the current page like this:

<li><a class="active" href="/products">Products</a></li>

You may want the entire menu item highlighted, not just the link text.

Form validation

Style a wrapper if it contains an invalid field:

.field:has(input:invalid) {
  border-color: red;
}

Cards with special content

Highlight a product card if it contains a sale badge:

.card:has(.sale-badge) {
  box-shadow: 0 0 0 2px orange;
}

Tables and lists

Style rows that contain selected checkboxes:

tr:has(input[type="checkbox"]:checked) {
  background: #f0f8ff;
}

CMS and generated markup

When HTML comes from a template or plugin and you cannot easily change where classes are added, :has() can reduce the need for JavaScript.

Take Quiz

Real Codebase Usage

In real codebases, developers usually choose among a few patterns depending on control over the markup and browser support needs.

1. Best when you control the HTML: put state on the element you want to style

<li class="active"><a href="/about">About</a></li>
li.active {
  font-weight: bold;
}

This is simple, clear, and usually best for maintainability.

2. Use :has() when markup cannot be changed

.nav-item:has(> a.active) {
  background: #eef;
}

This is common in CMS-driven layouts and reusable components.

3. Use JavaScript as a fallback when needed

If browser support requirements prevent using :has() alone, developers may add classes at runtime:

for (const link of document.querySelectorAll('li > a.active')) {
  link.parentElement.classList.add('active');
}

Then style normally:

li.active {
  background: #eef;
}

4. Validation and guard-style patterns

In large codebases, selectors are often kept specific and intentional:

.sidebar-menu li:has(> a.active) {
  color: var(--menu-active-color);
}

This avoids accidentally styling unrelated lists elsewhere.

5. Component-based styling

In design systems, :has() is often used to let a parent react to child state:

  • a button group with a selected button
  • a form row with an error message
  • a dialog containing a destructive action

The pattern is: style the container based on child state.

Common Mistakes

1. Expecting old CSS to support parent selection

This does not work in CSS Level 2:

li < a.active {
  color: red;
}

There is no < parent selector in CSS.

2. Styling the child when you meant to style the parent

This styles the anchor, not the li:

a.active {
  background: yellow;
}

If you want the parent, use:

li:has(> a.active) {
  background: yellow;
}

3. Forgetting the difference between direct child and any descendant

This matches only direct children:

li:has(> a.active) {}

This matches any nested descendant:

li:has(a.active) {}

If your HTML structure changes, the wrong version may stop matching.

4. Using :has() without checking browser support requirements

:has() is modern CSS. If you support older browsers, test carefully or provide a fallback.

5. Writing selectors that are too broad

This might affect every list item on the page:

li:has(> a.active) {
  font-weight: bold;
}

A safer version in a real app is often:

.main-nav li:has(> a.active) {
  font-weight: bold;
}

6. Overusing JavaScript when markup can be improved

If you control the server-side or template output, adding the class directly to the parent is often simpler than adding DOM-manipulation code later.

Comparisons

ApproachCan style parent based on child?Works in old CSS?Best use case
a.activeNoYesStyle the link itself
li.activeYes, if class is already on parentYesBest when you control HTML
li:has(> a.active)YesNoBest modern CSS solution
JavaScript adds class to parentYesYes, with JS enabledFallback for unsupported environments

:has() vs adding a class to the parent

OptionProsCons
Add class to parent in HTMLSimple, fast, clearRequires control over markup generation
Use :has()No markup change neededDepends on modern CSS support
Use JavaScriptFlexibleMore code and runtime work

CSS Level 2 vs modern CSS

FeatureCSS Level 2Modern CSS
Select child from parentYesYes
Select descendant from ancestorYesYes
Select parent from childNoYes, with :has()
React to nested state in CSS aloneVery limitedMuch better
Take Quiz

Cheat Sheet

/* Style the link itself */
a.active {
  color: red;
}

/* Style the li if it has a direct child anchor with class active */
li:has(> a.active) {
  color: red;
}

/* Style the li if it has any descendant anchor with class active */
li:has(a.active) {
  color: red;
}

Key rules

  • CSS Level 2 had no parent selector.
  • The old attempted syntax li < a.active is invalid.
  • Modern CSS uses :has() for parent-like matching.
  • > means direct child.
  • No > means any descendant.
  • If you control the HTML, putting the class on the parent is often the cleanest solution.

Quick decision guide

  • Need old-browser-friendly CSS only? Put the class on the parent if possible.
  • Cannot change markup and can use modern CSS? Use :has().
  • Need legacy support and cannot change markup? Use JavaScript to add a parent class.

Common pattern

.nav li:has(> a.active) {
  font-weight: bold;
  background: #eef;
}

FAQ

Is there a parent selector in CSS Level 2?

No. CSS Level 2 does not support selecting a parent based on a child element.

How do I select a parent <li> when the <a> has class active?

In modern CSS, use:

li:has(> a.active) {}

What does :has() do in CSS?

It matches an element if that element contains something matching the selector inside :has().

Can I use li < a.active in CSS?

No. That is not valid CSS syntax.

What if I need to support older browsers?

Use one of these approaches:

  • add the class to the parent in the HTML
  • use JavaScript to add a class to the parent
  • adjust your CMS template output

Is :has() a true parent selector?

People often call it that, but technically it is a relational pseudo-class that lets you match an element based on its descendants or related elements.

Should I use :has() or add a class to the parent?

If you control the markup, adding a class to the parent is usually simpler. If you cannot change the markup, :has() is very useful.

Can :has() check for nested descendants too?

Yes.

li:has(a.active) {}

matches if any descendant anchor inside the li has class active.

Take Quiz

Mini Project

Description

Build a small navigation menu where the active page link is marked on the <a> element, and style the parent <li> based on that child state. This mirrors a common CMS or framework scenario where you cannot change the generated HTML easily.

Goal

Create a menu that highlights the entire list item when its child link has the active class.

Requirements

  • Create an unordered list with at least three menu items.
  • Put the active class on one <a> element, not on the <li>.
  • Use CSS to style the parent <li> of the active link.
  • Add at least two visible styles, such as background color and bold text.
Take Quiz

Keep learning

Related questions

View more