In recent years, CSS has added many pseudo-classes, and it is no longer limited to a few browsers as in the past. Today, I want to introduce several CSS pseudo-classes and demonstrate their usage through practical examples.
:is
:where
:has
:lang
:focus-within
:first-child
and:last-child
:is
Previously, if we wanted to apply the same CSS rules to multiple selectors, we would write it like this:
.a-class,
.b-class {
font-size: 25px;
}
There is no problem with this approach, but with :is
, we can simplify the declaration:
:is(.a-class, .b-class) {
font-size: 25px;
}
Using :is
makes it easier to read when there are many selectors to apply. Although it is still in the Working Draft, according to the data from Can I use, 98.1% of users can use it.
In addition to single classes, it can also be used with other selectors:
:is(.a-class, .b-class) a:hover {
opacity: 0.8;
}
:where
:where
has the same purpose as :is
. However, there is a difference in specificity. When using :where
, it has no specificity, and its specificity is the same as *
. However, :is
is different and will have the specificity of the selector with the highest specificity in the list.
In this example, because of #page
, its specificity is higher than the class selector, so the final color will be red. If we use :where
, the selectors inside will not have additional specificity.
Therefore, the final applied color will be yellow. From the results above, the usage scenarios can be divided into:
- Use
:where()
for global styles (e.g., reset) to make it easier to override when needed. - Use
:is()
for local styles on multiple selectors to ensure consistent specificity for all selectors.
:has
:has
can match the parent element when the specified condition for the child element is met. It may sound a bit complicated, but it will be clearer with an example. One useful scenario is when we want to change the style of the parent element when the child element is focused.
Sometimes, when implementing a search function, we place the search icon next to the input box. When the input box is focused, we want not only the input box to have the focus style but also the entire part of the input box including the icon. Using input:focus
alone cannot achieve the desired effect.
In the past, the solution was to modify the DOM structure to make the CSS selector work or to use JavaScript to listen for focus events and add a class to let the parent element know that the input has been focused. With :has
, we can achieve this directly in CSS without changing the structure.
This way, when the input is focused, .form:has(input:focus)
will take effect and apply the styles to the parent element. Being able to match the parent element when the child element meets a condition has been a long-standing wish for front-end developers, and now we have taken a big step forward.
:has
has other use cases, especially when we want to apply a selector when the child element meets a certain condition.
For example, when switching themes, using body:has(input[type="checkbox"]:checked)
can switch color themes without JavaScript while maintaining a reasonable DOM structure.
For more use cases of :has
, you can refer to the webkit blog. :has
is a relatively newer pseudo-class, and its browser support is not as high yet (82.92%), so it may require some fallback plans if you want to use it.
:focus-within
For some experienced front-end developers, you may have noticed that the previous example can actually be replaced with :focus-within
. :focus-within
matches when a child element is focused, allowing us to modify the style of the parent element.
For example, if we want to add a box-shadow
to a form when it is focused, we can write it like this:
.container {
display: flex;
justify-content: center;
align-items: center;
}
label {
display: block;
}
label:not(:last-child) {
margin-bottom: 1em;
}
form {
width: fit-content;
background-color: #fff;
border: 2px solid #000;
padding: 20px;
border-radius: 8px;
}
form:focus-within {
box-shadow: 0px 3px 8px 3px rgba(0, 0, 0, 0.3);
}
However, :focus-within
can only be applied to focus and cannot provide the versatility for matching non-focus situations. If you need to match non-focus situations, you can use the more flexible :has
.
:lang
When implementing multilingual websites, we add a lang
attribute to the <html>
tag to indicate the current language of the website. Sometimes, we may need to display different fonts for different languages. Without using :lang
, we might need to write it like this:
html[lang="zh"] p { font-family: var(--font-zh); }
html[lang="en"] p { font-family: var(--font-en); }
It becomes a bit lengthy. With :lang
, we can simplify the selector syntax:
p:lang(en) {
font-family: var(--font-en);
}
p:lang(zh) {
font-family: var(--font-zh);
}
:first-child and :last-child
Although these two pseudo-classes are very common, I have noticed that many front-end developers are not aware of them. These two pseudo-classes can target the first or last child. The most common scenario is when specifying margin-right
and not wanting to apply it to the last element. In this case, we can write it like this:
.tab {
margin-right: 8px;
}
.tab:last-child {
margin-right: 0;
}
It can also be simplified using :not
:
.tag:not(:last-child) {
margin-right: 8px;
}
Why not just use JavaScript?
Some users may not have JavaScript enabled, especially for websites that mainly consist of static articles like blogs. However, we still want to provide a good reading experience without relying too much on JavaScript. The aforementioned pseudo-classes can help us solve layout issues. Why make things complicated when they can be simple?