Useful Pseudo-classes for Layout
In recent years, CSS has introduced many pseudo-classes, and they are no longer implemented by only a few browsers like they used to be. Today, I’d like to introduce several CSS pseudo-classes and show practical examples to help with styling and layout.
:is:where:has:lang:focus-within:first-childand:last-child
:is
Previously, when we wanted to apply the same CSS rules to multiple selectors, we would write:
.a-class,
.b-class {
font-size: 25px;
}
There is nothing wrong with this approach, but with :is, we can simplify the declaration to:
:is(.a-class, .b-class) {
font-size: 25px;
}
If you have many selectors to apply, using :is makes the code much easier to read. Although it is still a Working Draft, according to data from Can I use, it is supported for 98.1% of users.
In addition to individual classes, it can also be combined with other selectors:
:is(.a-class, .b-class) a:hover {
opacity: 0.8;
}
:where
:where functions the same way as :is. However, there is a difference in specificity. :where has no specificity weight—its specificity is equivalent to *. In contrast, :is takes on the weight of the most specific selector in its list.
Hello
In this example, because of #page, the specificity is higher than the class selector, so the resulting color is red. If you use :where, the selectors inside do not add extra specificity.
Hello
Therefore, the final applied color will be yellow. Based on the results above, their use cases can be divided into:
- Using
:where()for global styles (such as CSS resets), making them easy to override later. - Using
:is()for local styles across multiple selectors, ensuring all selectors share the same specificity.
:has
:has can match a parent element when specified conditions on its child elements are met. That explanation might sound a bit convoluted, so an example makes it much clearer. A very practical scenario is “changing the parent element’s style when a child element is focused.”
Sometimes when implementing a search feature, we place a search icon next to the input field. When the input is focused, we want not just the input field to have focus styling, but the entire input wrapper including the icon to reflect that focus state. In this case, input:focus alone cannot achieve what we want.
Past solutions involved either modifying the DOM structure so CSS selectors could work, or using JavaScript to listen for focus events and toggle a class on the parent element. With :has, we can achieve this directly in CSS without altering the DOM structure.
Focus input!
This way, when the input is focused, .form:has(input:focus) activates and applies the style to the parent element. Being able to “match a parent element when a child element meets a condition” has been a long-standing wish for front-end developers, and we have finally taken a big step forward.
:has has other use cases as well, especially whenever you want to apply styles based on a condition met by a child element.
For example, when switching themes, using body:has(input[type="checkbox"]:checked) allows you to toggle color themes without JavaScript while maintaining a clean, sensible DOM structure.
For more use cases of :has, you can check out the post on the WebKit blog. :has is a relatively new pseudo-class, and browser support is not quite as high yet (82.92%), so you may want to have a fallback plan when adopting it.
:focus-within
More experienced front-end developers might have noticed that the previous example could actually be replaced by :focus-within. :focus-within matches when any descendant element is focused, allowing you to modify the parent element’s styles.
For instance, if you want to add a box-shadow to the form when it is focused:
However, :focus-within only applies to focus states and cannot handle more diverse selection criteria. If you need to match conditions other than focus, you can turn to the more versatile :has.
:lang
When building multilingual websites, a lang attribute is typically added to <html> to indicate the current site language. Sometimes we need to display different fonts depending on the language. Without :lang, you might have to write:
html[lang="zh"] p { font-family: var(--font-zh); }
html[lang="en"] p { font-family: var(--font-en); }
This is somewhat verbose. Using :lang simplifies 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 are very common pseudo-classes, I have noticed quite a few front-end developers who are still unfamiliar with them. These pseudo-classes target the first or last child element. The most common scenario is wanting to set margin-right on items without applying it to the last element:
.tab {
margin-right: 8px;
}
.tab:last-child {
margin-right: 0;
}
This can also be simplified by pairing it with :not:
.tag:not(:last-child) {
margin-right: 8px;
}
Why Not Just Use JavaScript?
Some users may not have JavaScript enabled—especially on content-heavy static sites like blogs. When you want to maintain a good reading experience without relying on scripts, the pseudo-classes covered above can help solve these layout and styling challenges. Why make things complicated when they can be simple?
Related Posts
- Recreating My Room with Three.js Using React Three Fiber, I brought my real room into the browser—turning physical objects into an interactive table of contents, and using spatial memory to tell the story of my life and work over the past few years.
- Things to Keep in Mind When Using Images in Frontend Development Expanding on Jake Archibald's article, this post organizes how modern responsive images should be written: why width/height are still necessary, when to use CSS aspect-ratio, how to choose between AVIF and WebP, and using picture/source/srcset for art direction on mobile devices.
- CSS field-sizing — Auto-resize Form Elements with a Single Line of CSS Previously, auto-resizing a textarea required listening to scrollHeight in JavaScript. With CSS field-sizing: content, a single line replaces it all, supporting textarea, input, and select. This article covers the pain points of older approaches and how to use field-sizing.
- Make Your Link Underlines Look Better: text-underline-offset By default, underlines sit very close to the text. Some designers dislike this look, and personally, I don't think it looks great either.