Quantity Queries for “islands of elements” with the same class, thanks to CSS :has()

Leveraging :has() to detect islands of elements that have the same class, and select the first and last one from those islands.


This content originally appeared on Bram.us and was authored by Bramus!

The CSS :has() selector has unlocked a bunch of new possibilities to select elements using CSS. A while ago I detailed how you can select elements based on the number of children they have using CSS :has(). Today we’ll look into using :has() to select the first, second, …, last element from an “island of elements” that all have a certain class.

~

# The code

If you’re just here for the code, here it is. You can also see the code in action in the demo below.

/* :first-in-island-of-class(.special) – Selects the first element from an island of siblings have a certain class */
.special:first-child,
*:not(.special):has(+ .special) + .special {
    …
}

/* :last-in-island-of-class(.special) – Selects the last element from an island of siblings have a certain class */
.special:not(:has(+ .special)) {
    …
}

/* :single-in-island-of-class(.special) – Selects the element that forms a single-element island with that a certain class */
*:not(.special):has(+ .special) + .special:not(:has(+ .special)),
.special:first-child:not(:has(+ .special)) {
    …
}

If you want to know how these work – along with even more selectors such as a :nth-in-island-of-class(.special) selector – keep on reading 🙂

~

# Islands?

Before we jump in, let’s make sure we’re using the same lingo here. With “islands of elements” I mean groups of adjacent sibling elements that can be grouped together. For example, consider the following list of child elements:

  1. no class
  2. .special
  3. .special
  4. .special
  5. no class
  6. .special
  7. .special
  8. no class
  9. no class
  10. .special
  11. no class

Elements 2, 3,and 4 form an island, as they can be grouped together because they share the same class. Same with elements 6 and 7, they also form an island.

Entry 10 is also an island, even though it only consists of only individual element.

~

# The selectors

Using :has(), we can detect these islands and style the first and last elements of each island.

Note that the selectors created below all have a rather high specificity. To keep it low, I suggest wrapping them inside a :where() which nullifies the specificity. If you want to bump up the specificity again, you could tack on a :not().

E.g. :where(…):not(.foo) will have a specificity of (0,1,0).

~

:first-in-island-of-class(.special)

This selects the first element from an island of siblings have a certain class. Multiple children in a parent can be selected, as there can be several “islands” of elements with that class.

.special:first-child,
*:not(.special):has(+ .special) + .special {
    …
}

*:not(.special):has(+ .special) first selects all elements that are not .special but that are followed by a .special. Using + .special we then jump from the matched element to its adjacent .special sibling. To also catch .special elements that are the first child, .special:first-child is used.

~

:last-in-island-of-class(.special)

This selects the last element from an island of siblings have a certain class. Multiple children in a parent can be selected, as there can be several “islands” of elements with that class.

.special:not(:has(+ .special)) {
    …
}

It works by selecting any .special that is not directly followed by another .special.

~

:single-in-island-of-class(.special)

By combining :first-in-island-of-class(.special) and :last-in-island-of-class(.special), it’s possible to detect islands with a class that consist of only 1 element.

*:not(.special):has(+ .special) + .special:not(:has(+ .special)),
.special:first-child:not(:has(+ .special)) {
    …
}

~

# Demo

See the Pen by Bramus (@bramus) on CodePen.

~

# More Selectors

We are not limited to only selecting the first or last element in an island. It’s possible to select any element at position n in an island …

:nth-in-island-of-class(.special)

By adding more + .special clauses to the selection, it’s possible to select the :2nd-in-island-of-class, :3rd-in-island-of-class, etc.

/* :2nd-in-island-of-class(.special) */
.special:first-child:has(+ .special) + .special,
*:not(.special):has(+ .special + .special) + .special + .special {
   …
}

/* :3rd-in-island-of-class(.special) */
.special:first-child:has(+ .special + .special) + .special + .special,
*:not(.special):has(+ .special + .special + .special) + .special + .special + .special {
   …
}

~

:nth-last-in-island-of-class(.special)

Same goes for a :nth-last-in-island-of-class selector: add more + .special clauses to the condition. Then tack an extra :has(+ .special) onto the selector to jump to the correct element.

/* :2nd-last-child-in-island-of-class(.special) */
.special:not(:has(+ .special + .special)):has(+ .special) {
	color: red;
}

/* :3rd-last-child-in-island-of-class(.special) */
.special:not(:has(+ .special + .special + .special)):has(+ .special + .special) {
	color: green;
}

~

Selector Generator

Use the pen below to generate :nth-in-island-of-class(.special) and :nth-last-in-island-of-class(.special) selectors. Use the dropdowns to change the value for n as well as the type of selection. The selector is not limited to classes, but is limited to a compound selector.

See the Pen by Bramus (@bramus) on CodePen.

~

# Browser Support

These selectors are supported by all browsers that have :has() support. At the time of writing this does not include Firefox.

👨‍🔬 Flipping on the experimental :has() support in Firefox doesn’t do the trick either. Its implementation is still experimental as it doesn’t support all types of selection yet. Relative Selector Parsing (i.e. a:has(~ b)) is one of those features that’s not supported yet – Tracking bug: #1774588

~

# Spread the word

To help spread the contents of this post, feel free to retweet its announcement tweet:

~

🔥 Like what you see? Want to stay in the loop? Here's how:


This content originally appeared on Bram.us and was authored by Bramus!


Print Share Comment Cite Upload Translate Updates
APA

Bramus! | Sciencx (2022-12-13T14:52:03+00:00) Quantity Queries for “islands of elements” with the same class, thanks to CSS :has(). Retrieved from https://www.scien.cx/2022/12/13/quantity-queries-for-islands-of-elements-with-the-same-class-thanks-to-css-has/

MLA
" » Quantity Queries for “islands of elements” with the same class, thanks to CSS :has()." Bramus! | Sciencx - Tuesday December 13, 2022, https://www.scien.cx/2022/12/13/quantity-queries-for-islands-of-elements-with-the-same-class-thanks-to-css-has/
HARVARD
Bramus! | Sciencx Tuesday December 13, 2022 » Quantity Queries for “islands of elements” with the same class, thanks to CSS :has()., viewed ,<https://www.scien.cx/2022/12/13/quantity-queries-for-islands-of-elements-with-the-same-class-thanks-to-css-has/>
VANCOUVER
Bramus! | Sciencx - » Quantity Queries for “islands of elements” with the same class, thanks to CSS :has(). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/12/13/quantity-queries-for-islands-of-elements-with-the-same-class-thanks-to-css-has/
CHICAGO
" » Quantity Queries for “islands of elements” with the same class, thanks to CSS :has()." Bramus! | Sciencx - Accessed . https://www.scien.cx/2022/12/13/quantity-queries-for-islands-of-elements-with-the-same-class-thanks-to-css-has/
IEEE
" » Quantity Queries for “islands of elements” with the same class, thanks to CSS :has()." Bramus! | Sciencx [Online]. Available: https://www.scien.cx/2022/12/13/quantity-queries-for-islands-of-elements-with-the-same-class-thanks-to-css-has/. [Accessed: ]
rf:citation
» Quantity Queries for “islands of elements” with the same class, thanks to CSS :has() | Bramus! | Sciencx | https://www.scien.cx/2022/12/13/quantity-queries-for-islands-of-elements-with-the-same-class-thanks-to-css-has/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.