Conditional CSS with :has and :nth-last-child
We will test with CSS to know if the variety of a bunch factor is lower than or equal to a quantity. For instance, having a grid with three or extra gadgets. You could be questioning, why is that even wanted. In some instances, a element or a structure would possibly change based mostly on the variety of baby parts.
This has been in CSS for years, nevertheless it turning into extra highly effective now with CSS :has
. We will mix each the nth-last-child
selector together with :has
to do magic, sure! You heard that proper.
Earlier this 12 months, I revealed a submit titled Conditional CSS the place I confirmed how some CSS options assist us to create conditional UIs. On this article, I’ll spotlight just a few examples of the place we will mix a CSS selector with :has
to have a conditional element/structure states.
Introduction to :nth-last-child
One of many essential elements on this article is the :nth-last-child
pseudo-class. We will use that selector to mock counting baby parts.
Let’s discover the way it works. I’ll attempt my greatest to elucidate the way it works with plain phrases.
Take into account the next determine:
We’ve got a listing of 5 playing cards. I’ll use that for instance to exhibit what we will do with :nth-last-child
.
Within the following CSS, we’ve n + 3
which suggests:
li:nth-last-child(n + 3) {
/* types */
}
Choose the primary three gadgets from the tip, counting from the third merchandise.
Let’s take a more in-depth look. First, we have to depend 3 gadgets from the tip. With that, the third merchandise is definitely the primary merchandise that we are going to depend until the tip of the checklist.
After we depend from the third merchandise until the tip, listed below are the chosen gadgets:
Amount queries limitations in CSS
As defined in this great article by Heydon Pickering, we will use the :nth-last-child
as a amount question.
Take into account the next determine:
We’ve got a listing of knowledge that’s displayed in another way when we’ve 5 or extra gadgets.
<ul>
<li></li>
<li></li>
<li></li>
<!-- extra gadgets -->
</ul>
li {
/* default types */
}
/* If the checklist has 5 or extra gadgets */
li:nth-last-child(n + 5),
li:nth-last-child(n + 5) ~ li {
width: 50%;
show: inline-block;
border-bottom: 0;
}
Whereas that works, it’s nonetheless a bit limiting in some methods.
It’s not potential to model the father or mother based mostly on the variety of parts.
Think about that we have to add show: flex
to every <li>
when there are 5 or extra gadgets. We will’t do this with the :nth-last-child
pseudo-class selector.
The reason being that including show: flex
will drive every merchandise to remain in its personal row, which doesn’t align with the design to realize.
li:nth-last-child(n + 5),
li:nth-last-child(n + 5) ~ li {
width: 50%;
show: flex;
flex-direciton: column;
}
We will repair that with show: inline-flex
as a substitute, nevertheless it’s nonetheless not the optimum resolution for me. The reason being that the browser will account for the spacing between the HTML parts, they need to be like that:
<ul>
<li></li><li></li><li></li>
<!-- extra gadgets -->
</ul>
If we don’t do this, show: inline-flex
may have the identical impact as show: flex
. One hack to repair that’s to scale back the width by 1%.
li:nth-last-child(n + 5),
li:nth-last-child(n + 5) ~ li {
width: 49%;
show: flex;
flex-direciton: column;
}
Making them work on completely different viewport sizes
With out the flexibility to have management over the father or mother, it’s not that simple to model the structure of the itemizing. For instance, when the container or viewport width is smaller, we have to present 1 merchandise per row.
Extra work to handle the spacing
When there are 3 gadgets or fewer, the spacing is horizontal, and when it’s 5 or extra, the spacing is vertical. We will handle that manually by flipping the margin from horizontal to vertical, or by utilizing CSS hole
with Flexbox. However once more, we’re pressured to make use of inline-flex
for that case.
The CSS :nth-last-child
pseudo-class is the important thing to constructing conditional layouts. By combining it with the CSS :has
selector, we will test if a father or mother factor has at the very least a particular variety of gadgets and elegance it accordingly. The probabilities are limitless!
Use instances and examples
Grid that adjustments based mostly on the variety of baby gadgets
When we have to change a grid based mostly on the variety of gadgets, this isn’t potential with the present CSS. In CSS grid, we will use the minmax()
operate to have a dynamic grid that adjustments based mostly on the out there area.
Right here is my tackle CSS grid minmax()
:
.checklist {
show: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
hole: 1rem;
}
The end result would possibly appear like this:
This isn’t good. We don’t have a lot management, as we have to tweak the worth of 150px
within the minmax()
. It could work nice when having 4 gadgets or much less, and break for five gadgets or extra.
The answer? We will test with CSS :has
if there are greater than 5 gadgets or extra, and alter the minmax()
worth based mostly on that.
/* default grid */
.checklist {
--item-size: 200px;
show: grid;
grid-template-columns: repeat(auto-fit, minmax(var(--item-size), 1fr));
hole: 1rem;
}
/* If the grid has 5+ gadgets, change the --item-size width to 150px */
.checklist:has(li:nth-last-child(n + 5)) {
--item-size: 150px;
}
I solely modified the --item-size
variable to make the code simpler to learn and to keep away from duplication.
See the next video and spot how the grid columns change as I add or take away gadgets.
Isn’t that highly effective?
Within the following determine, we’ve a header that ought to change its structure when the navigation gadgets are 4 or extra. With CSS :has
and :nth-last-child
, we will detect that and alter the structure.
.site-header:has(li:nth-last-child(n + 4)) {
.site-header__wrapper > * {
flex: preliminary;
}
.site-header__start {
order: 2;
}
.site-header__middle {
order: -1;
text-align: begin;
}
.site-header__end {
margin-left: auto;
}
}
The above is the Sass
code. It’d look a bit an excessive amount of when written in vanilla CSS.
.site-header:has(li:nth-last-child(n + 4)) .site-header__wrapper > * {
flex: preliminary;
}
.site-header:has(li:nth-last-child(n + 4)) .site-header__start {
order: 2;
}
.site-header:has(li:nth-last-child(n + 4)) .site-header__middle {
order: -1;
text-align: begin;
}
.site-header:has(li:nth-last-child(n + 4)) .site-header__end {
margin-left: auto;
}
Can we do higher? Sure! However this isn’t supported nicely (but!). We will add a boolean CSS variable that can be toggled when the header has 4 gadgets or extra, after which use a style query to alter the header.
.site-header:has(li:nth-last-child(n + 4)) {
--layout-2: true;
}
With that, we set the variable --layout-2
when the navigation gadgets are 4 or extra.
/* This may solely works if the --layout-2 CSS variable is ready */
@container model(--layout-2: true) {
.site-header__wrapper {
> * {
flex: preliminary;
}
}
.site-header__start {
order: 2;
}
.site-header__middle {
order: -1;
text-align: begin;
}
.site-header__end {
margin-left: auto;
}
}
For me, this appears to be like clear and a lot better than nesting all CSS types throughout the :has
selector.
Dynamic information part
The next is a information part design that ought to change its structure when the variety of gadgets is 3 or extra.
By combining CSS :has
and :nth-last-child
, we will create a toggle CSS variable that can be checked by a method question.
First, I’ll assume that the default card model is the horizontal one.
<div class="structure">
<article class="card"></article>
<article class="card"></article>
<article class="card"></article>
</div>
.structure {
show: grid;
grid-gap: 1rem;
}
.card {
show: flex;
hole: 1rem;
align-items: middle;
}
After that, I must test the variety of .card
parts.
.structure:has(.card:nth-last-child(n + 4)) {
--layout-4: true;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
Now, we’ve the CSS variable --layout-4
that can be toggled solely when we’ve 4 gadgets or extra. We will test that with a method question and replace the .card
model accordingly.
@container model(--layout-4: true) {
.card {
flex-direction: column;
}
.card__thumb {
flex: 1;
width: 100%;
aspect-ratio: 4 / 3;
}
}
Modal actions
In a design system, we would must dynamically management the alignment of the modal actions based mostly on what number of actions we’ve.
Take into account the next determine:
For instance, if we’ve one motion, it needs to be centered. In any other case, right-align them.
Right here is the CSS:
.modal__footer {
show: flex;
justify-content: middle;
hole: 0.5rem;
}
/* If there are 2 buttons or extra */
.modal__footer:has(a:nth-last-child(n + 2)) {
justify-content: flex-end;
}
Easy, isn’t it? Here’s a demo in motion.
Consumer avatars
On editorial web sites, an article could be written by a number of authors. A typical sample is to stack the writer photos with adverse spacing when we’ve a number of authors.
Through the use of amount queries alone, we will obtain the minimal, which is to:
- Add adverse spacing (stack the avatars on high of one another).
- Shrink the avatar measurement when there are a number of ones.
img:nth-last-child(n+2) ~ img {
border: 2px stable #fff;
margin-left: -0.25rem;
width: 30px;
peak: 30px;
}
The above works, nevertheless it’s limiting. What if we need to model the container itself? Nicely, that’s the place CSS :has
turns into highly effective.
First, we have to test and toggle a CSS variable:
.post-author:has(img:nth-last-child(n + 2)) {
--multiple-avatars: true;
}
If that CSS variable is true, we then apply the types for a number of avatars:
@container model(--multiple-avatars: true) {
.avatars-list {
show: flex;
background-color: #efefef;
padding: 8px 12px;
border-radius: 50px;
}
img:not(:first-child) {
border: stable 2px #fff;
margin-left: -0.25rem;
}
}
Take a look at the next video:
Timeline
One other attention-grabbing instance the place conditional CSS works nicely is a timeline element.
On this instance, I need the timeline to modify from a vertical itemizing to an alternating model when it has 4 or extra gadgets.
First, I used the :nth-last-child
with CSS :has
:
.timeline-wrapper:has(.timeline__item:nth-last-child(n + 4)) {
--alternating: true;
}
If the above is met, the next CSS can be utilized:
@container model(--alternating: true) {
/* Alternating timeline types. */
}
What’s helpful about utilizing model queries right here is that we will reuse that styling on one other web page. It doesn’t must be a conditional CSS.
I would do one thing like this:
.timeline-wrapper--page-10 {
--alternating: true;
}
Please don’t thoughts .timeline-wrapper--page-10
, that is an intentional random class title. The CSS variable will be assigned anyplace we wish, and the CSS will work out of the field.
Write it as soon as, and it really works for a lot of instances.
Observe: this demo breaks in Chrome Canary and I suppose the reason being that I’m utilizing pseudo-elements inside model queries. I’m investigating that in additional element and can replace the article as I bought extra data.
Grid of logos
One of many tough issues to take care of in CSS is aligning a number of logos and ensuring all of them look good. With conditional CSS, we will detect the variety of logos and shrink their measurement a bit.
ul:has(li:nth-last-child(n + 8)) img {
max-width: 160px;
peak: 35px;
}
Outro
This was one of many attention-grabbing articles that I labored on. Combining trendy CSS options can let to thrilling new methods to construct layouts, and this text’s examples had been no exception.
Altering a method based mostly on the variety of gadgets may not be a one-off utilization; it may be extracted to completely different use instances. Through the use of model queries, we will write as soon as and reuse them all over the place.