CSS has notoriously lacked a way to directly select a parent element based on
its children. This has been a top request by developers for many years. The
:has()
selector, now supported by all major browsers, solves this. Before
:has()
, you'd often chain long selectors or add classes for styling hooks. Now
you can style based on an element's relationship with its descendants. Read more
about the :has()
selector in
CSS Wrapped 2023 and
5 CSS snippets every frontend developer should know.
Although this selector seems small, it can enable a huge number of use cases.
This article shows some use cases that ecommerce companies unlocked with the
:has()
selector.
:has()
is part of Baseline Newly Available.
Check out the full series that this article is part of, which discusses how ecommerce companies enhanced their website using new CSS and UI features.
Policybazaar
With the
:has()
selector, we were able to eliminate JavaScript based validation of the user's selection and replace it with a CSS solution which is working seamlessly for us with the same experience as before.—Aman Soni, Tech Lead, Policybazaar
Policybazaar's Investment team cleverly applied the :has()
selector to provide a
clear visual indication for users that are comparing plans. The following image
shows two types of plans within the comparison UI (yellow and blue). Each plan
can only be compared with its own type. By using :has()
, when a user selects one
type of plan the other plan type is unable to be selected.
Code
:has()
gives you access to style parent elements and their children. The
following code checks if a parent container has a .disabled-group
class set.
If it does, the card is greyed out, and the "Add" button is prevented from
reacting to clicks by setting pointer-events
to none
.
.plan-group-container:has(.disabled-group) {
opacity: 0.5;
filter: grayscale(100%);
}
.plan-group-container:has(.disabled-section) .button {
pointer-events: none;
border-color: #B5B5B5;
color: var(--text-primary-38-color);
background: var(--input-border-color);
}
The health team at Policybazaar implemented
a slightly different use case. They have an inline quiz for the user and use
:has()
to check the question checkbox status to see if the question was
answered. If it was, an animation is applied to transition to the next question.
Code
In the plan comparison example, :has()
was used to check the presence of a
class. You can also check the state of an input element such as a checkbox using
:has(input:checked)
. In the visual showing the quiz, each question in the
purple banner is a checkbox. Policybazaar checks if the question has been
answered using :has(input:checked)
and if it has, trigger an animation using
animation: quesSlideOut 0.3s 0.3s linear forwards
to slide to the next
question. See how this works in the following code.
.segment_banner__wrap__questions {
position: relative;
animation: quesSlideIn 0.3s linear forwards;
}
.segment_banner__wrap__questions:has(input:checked) {
animation: quesSlideOut 0.3s 0.3s linear forwards;
}
@keyframes quesSlideIn {
from {
transform: translateX(50px);
opacity: 0;
}
to {
transform: translateX(0px);
opacity: 1;
}
}
@keyframes quesSlideOut {
from {
transform: translateX(0px);
opacity: 1;
}
to {
transform: translateX(-50px);
opacity: 0;
}
}
Tokopedia
Tokopedia used :has()
to create an overlay image if the product thumbnail
contains a video. If the product thumbnail contains a .playIcon
class, a CSS
overlay is added. Here, the :has() selector is used together with the &
nesting selector within the overarching .thumbnailWrapper
class which applies
to all the thumbnails. This creates more modular and readable CSS.
Code
The following code uses the
CSS selectors and combinators
(&
and >
) and nesting with :has()
to style the thumbnail.
For non-supporting
browsers, the regular additional CSS class rule is used as the fallback. The
@supports selector(:has(*))
rule is also used to check for browser support.
Therefore, the overall experience is the same across the browser versions.
export const thumbnailWrapper = css`
padding: 0;
margin-right: 7px;
border: none;
outline: none;
background: transparent;
> div {
width: 64px;
height: 64px;
overflow: hidden;
cursor: pointer;
border-color: ;
position: relative;
border: 2px solid ${NN0};
border-radius: 8px;
transition: border-color 0.25s;
&.active {
border-color: ${GN500};
}
@supports selector(:has(*)) {
&:has(.playIcon) {
&::after {
content: '';
display: block;
background: rgba(0, 0, 0, 0.2);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
}
& > .playIcon {
position: absolute;
top: 25%;
left: 25%;
width: 50%;
height: 50%;
text-align: center;
z-index: 1;
}
}
`;
Things to consider when using :has()
Combine :has()
with other selectors to create a more complex condition. Check
out some examples in has() the family selector.
Resources:
- CSS Wrapped 2023
- :has(): the family selector
- Demos :has()
- Do you want to report a bug or request a new feature? We want to hear from you!
Explore the other articles in this series which talks about how ecommerce companies benefited from using new CSS and UI features such as Scroll-driven animations, view transitions, popover and container queries.