Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Sunshine And March Vibes (2025 Wallpapers Edition)

      June 2, 2025

      The Case For Minimal WordPress Setups: A Contrarian View On Theme Frameworks

      June 2, 2025

      How To Fix Largest Contentful Paint Issues With Subpart Analysis

      June 2, 2025

      How To Prevent WordPress SQL Injection Attacks

      June 2, 2025

      How Red Hat just quietly, radically transformed enterprise server Linux

      June 2, 2025

      OpenAI wants ChatGPT to be your ‘super assistant’ – what that means

      June 2, 2025

      The best Linux VPNs of 2025: Expert tested and reviewed

      June 2, 2025

      One of my favorite gaming PCs is 60% off right now

      June 2, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      `document.currentScript` is more useful than I thought.

      June 2, 2025
      Recent

      `document.currentScript` is more useful than I thought.

      June 2, 2025

      Adobe Sensei and GenAI in Practice for Enterprise CMS

      June 2, 2025

      Over The Air Updates for React Native Apps

      June 2, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      You can now open ChatGPT on Windows 11 with Win+C (if you change the Settings)

      June 2, 2025
      Recent

      You can now open ChatGPT on Windows 11 with Win+C (if you change the Settings)

      June 2, 2025

      Microsoft says Copilot can use location to change Outlook’s UI on Android

      June 2, 2025

      TempoMail — Command Line Temporary Email in Linux

      June 2, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»News & Updates»How to Wait for the sibling-count() and sibling-index() Functions

    How to Wait for the sibling-count() and sibling-index() Functions

    January 13, 2025

    New features don’t just pop up in CSS (but I wish they did). Rather, they go through an extensive process of discussions and considerations, defining, writing, prototyping, testing, shipping handling support, and many more verbs that I can’t even begin to imagine. That process is long, and despite how much I want to get my hands on a new feature, as an everyday developer, I can only wait.

    I can, however, control how I wait: do I avoid all possible interfaces or demos that are possible with that one feature? Or do I push the boundaries of CSS and try to do them anyway?

    As ambitious and curious developers, many of us choose the latter option. CSS would grow stagnant without that mentality. That’s why, today, I want to look at two upcoming functions: sibling-count() and sibling-index(). We’re waiting for them — and have been for several years — so I’m letting my natural curiosity get the best of me so I can get a feel for what to be excited about. Join me!

    The tree-counting functions

    At some point, you’ve probably wanted to know the position of an element amongst its siblings or how many children an element has to calculate something in CSS, maybe for some staggering animation in which each element has a longer delay, or perhaps for changing an element’s background-color depending on its number of siblings. This has been a long-awaited deal on my CSS wishlists. Take this CSSWG GitHub Issue from 2017:

    Feature request. It would be nice to be able to use the counter() function inside of calc() function. That would enable new possibilities on layouts.

    However, counters work using strings, rendering them useless inside a calc() function that deals with numbers. We need a set of similar functions that return as integers the index of an element and the count of siblings. This doesn’t seem too much to ask. We can currently query an element by its tree position using the :nth-child() pseudo-selector (and its variants), not to mention query an element based on how many items it has using the :has() pseudo-selector.

    Luckily, this year the CSSWG approved implementing the sibling-count() and sibling-index() functions! And we already have something in the spec written down:

    The sibling-count() functional notation represents, as an <integer>, the total number of child elements in the parent of the element on which the notation is used.

    The sibling-index() functional notation represents, as an <integer>, the index of the element on which the notation is used among the children of its parent. Like :nth-child(), sibling-index() is 1-indexed.

    How much time do we have to wait to use them? Earlier this year Adam Argyle said that “a Chromium engineer mentioned wanting to do it, but we don’t have a flag to try it out with yet. I’ll share when we do!” So, while I am hopeful to get more news in 2025, we probably won’t see them shipped soon. In the meantime, let’s get to what we can do right now!

    Rubbing two sticks together

    The closest we can get to tree counting functions in terms of syntax and usage is with custom properties. However, the biggest problem is populating them with the correct index and count. The simplest and longest method is hardcoding each using only CSS: we can use the nth-child() selector to give each element its corresponding index:

    li:nth-child(1) {
      --sibling-index: 1;
    }
    
    li:nth-child(2) {
      --sibling-index: 2;
    }
    
    li:nth-child(3) {
      --sibling-index: 3;
    }
    
    /* and so on... */

    Setting the sibling-count() equivalent has a bit more nuance since we will need to use quantity queries with the :has() selector. A quantity query has the following syntax:

    .container:has(> :last-child:nth-child(m)) { }

    …where m is the number of elements we want to target. It works by checking if the last element of a container is also the nth element we are targeting; thus it has only that number of elements. You can create your custom quantity queries using this tool by Temani Afif. In this case, our quantity queries would look like the following:

    ol:has(> :nth-child(1)) {
      --sibling-count: 1;
    }
    
    ol:has(> :last-child:nth-child(2)) {
      --sibling-count: 2;
    }
    
    ol:has(> :last-child:nth-child(3)) {
      --sibling-count: 3;
    }
    
    /* and so on... */

    This example is intentionally light on the number of elements for brevity, but as the list grows it will become unmanageable. Maybe we could use a preprocessor like Sass to write them for us, but we want to focus on a vanilla CSS solution here. For example, the following demo can support up to 12 elements, and you can already see how ugly it gets in the code.

    CodePen Embed Fallback

    That’s 24 rules to know the index and count of 12 elements for those of you keeping score. It surely feels like we could get that number down to something more manageable, but if we hardcode each index we are bound increase the amount of code we write. The best we can do is rewrite our CSS so we can nest the --sibling-index and --sibling-count properties together. Instead of writing each property by itself:

    li:nth-child(2) {
      --sibling-index: 2;
    }
    
    ol:has(> :last-child:nth-child(2)) {
      --sibling-count: 2;
    }

    We could instead nest the --sibling-count rule inside the --sibling-index rule.

    li:nth-child(2) {
      --sibling-index: 2;
    
      ol:has(> &:last-child) {
        --sibling-count: 2;
      }
    }

    While it may seem wacky to nest a parent inside its children, the following CSS code is completely valid; we are selecting the second li element, and inside, we are selecting an ol element if its second li element is also the last, so the list only has two elements. Which syntax is easier to manage? It’s up to you.

    CodePen Embed Fallback

    But that’s just a slight improvement. If we had, say, 100 elements we would still need to hardcode the --sibling-index and --sibling-count properties 100 times. Luckily, the following method will increase rules in a logarithmic way, specifically base-2. So instead of writing 100 rules for 100 elements, we will be writing closer to 10 rules for around 100 elements.

    Flint and steel

    This method was first described by Roman Komarov in October last year, in which he prototypes both tree counting functions and the future random() function. It’s an amazing post, so I strongly encourage you to read it.

    This method also uses custom properties, but instead of hardcoding each one, we will be using two custom properties that will build up the --sibling-index property for each element. Just to be consistent with Roman’s post, we will call them --si1 and --si2, both starting at 0:

    li {
      --si1: 0;
      --si2: 0;
    }

    The real --sibling-index will be constructed using both properties and a factor (F) that represents an integer greater or equal to 2 that tells us how many elements we can select according to the formula sqrt(F) - 1. So…

    • For a factor of 2, we can select 3 elements.
    • For a factor of 3, we can select 8 elements.
    • For a factor of 5, we can select 24 elements.
    • For a factor of 10, we can select 99 elements.
    • For a factor of 25, we can select 624 elements.

    As you can see, increasing the factor by one will give us exponential gains on how many elements we can select. But how does all this translate to CSS?

    The first thing to know is that the formula for calculating the --sibling-index property is calc(F * var(--si2) + var(--si1)). If we take a factor of 3, it would look like the following:

    li {
      --si1: 0;
      --si2: 0;
    
      /* factor of 3; it's a harcoded number */
      --sibling-index: calc(3 * var(--si2) + var(--si1));
    }

    The following selectors may be random but stay with me here. For the --si1 property, we will write rules selecting elements that are multiples of the factor and offset them by one 1 until we reach F - 1, then set --si1 to the offset. This translates to the following CSS:

    li:nth-child(Fn + 1) { --si1: 1; }
    li:nth-child(Fn + 2) { --si1: 2; }
    /* ... */
    li:nth-child(Fn+(F-1)) { --si1: (F-1) }

    So if our factor is 3, we will write the following rules until we reach F-1, so 2 rules:

    li:nth-child(3n + 1) { --si1: 1; }
    li:nth-child(3n + 2) { --si1: 2; }

    For the --si2 property, we will write rules selecting elements in batches of the factor (so if our factor is 3, we will select 3 elements per rule), going from the last possible index (in this case 8) backward until we simply are unable to select more elements in batches. This is a little more convoluted to write in CSS:

    li:nth-child(n + F*1):nth-child(-n + F*1-1){--si2: 1;}
    li:nth-child(n + F*2):nth-child(-n + F*2-1){--si2: 2;}
    /* ... */
    li:nth-child(n+(F*(F-1))):nth-child(-n+(F*F-1)) { --si2: (F-1) }

    Again, if our factor is 3, we will write the following two rules:

    li:nth-child(n + 3):nth-child(-n + 5) {
      --si2: 1;
    }
    li:nth-child(n + 6):nth-child(-n + 8) {
      --si2: 2;
    }

    And that’s it! By only setting those two values for --si1 and --si2 we can count up to 8 total elements. The math behind how it works seems wacky at first, but once you visually get it, it all clicks. I made this interactive demo in which you can see how all elements can be reached using this formula. Hover over the code snippets to see which elements can be selected, and click on each snippet to combine them into a possible index.

    CodePen Embed Fallback

    If you crank the elements and factor to the max, you can see that we can select 48 elements using only 14 snippets!

    Wait, one thing is missing: the sibling-count() function. Luckily, we will be reusing all we have learned from prototyping --sibling-index. We will start with two custom properties: --sc1 and --sc1 at the container, both starting at 0 as well. The formula for calculating --sibling-count is the same.

    ol {
      --sc1: 0;
      --sc2: 0;
    
      /* factor of 3; also a harcoded number */
      --sibling-count: calc(3 * var(--sc2) + var(--sc1));
    }

    Roman’s post also explains how to write selectors for the --sibling-count property by themselves, but we will use the :has() selection method from our first technique so we don’t have to write extra selectors. We can cram those --sc1 and --sc2 properties into the rules where we defined the sibling-index() properties:

    /* --si1 and --sc1 */
    li:nth-child(3n + 1) {
      --si1: 1;
    
      ol:has(> &:last-child) {
        --sc1: 1;
      }
    }
    
    li:nth-child(3n + 2) {
      --si1: 2;
    
      ol:has(> &:last-child) {
        --sc1: 2;
      }
    }
    
    /* --si2 and --sc2 */
    li:nth-child(n + 3):nth-child(-n + 5) {
      --si2: 1;
    
      ol:has(> &:last-child) {
        --sc2: 1;
      }
    }
    
    li:nth-child(n + 6):nth-child(-n + 8) {
      --si2: 2;
    
      ol:has(> &:last-child) {
        --sc2: 2;
      }
    }

    This is using a factor of 3, so we can count up to eight elements with only four rules. The following example has a factor of 7, so we can count up to 48 elements with only 14 rules.

    CodePen Embed Fallback

    This method is great, but may not be the best fit for everyone due to the almost magical way of how it works, or simply because you don’t find it aesthetically pleasing. While for avid hands lighting a fire with flint and steel is a breeze, many won’t get their fire started.

    Using a flamethrower

    For this method, we will use once again custom properties to mimic the tree counting functions, and what’s best, we will write less than 20 lines of code to count up to infinity—or I guess to 1.7976931348623157e+308, which is the double precision floating point limit!

    We will be using the Mutation Observer API, so of course it takes JavaScript. I know that’s like admitting defeat for many, but I disagree. If the JavaScript method is simpler (which it is, by far, in this case), then it’s the most appropriate choice. Just as a side note, if performance is your main worry, stick to hard-coding each index in CSS or HTML.

    First, we will grab our container from the DOM:

    const elements = document.querySelector("ol");

    Then we’ll create a function that sets the --sibling-index property in each element and the --sibling-count in the container (it will be available to its children due to the cascade). For the --sibling-index, we have to loop through the elements.children, and we can get the --sibling-count from elements.children.length.

    const updateCustomProperties = () => {
      let index = 1;
    
      for (element of elements.children) {
        element.style.setProperty("--sibling-index", index);
        index++;
      }
    
      elements.style.setProperty("--sibling-count", elements.children.length);
    };

    Once we have our function, remember to call it once so we have our initial tree counting properties:

    updateCustomProperties();

    Lastly, the Mutation Observer. We need to initiate a new observer using the MutationObserver constructor. It takes a callback that gets invoked each time the elements change, so we write our updateCustomProperties function. With the resulting observer object, we can call its observe() method which takes two parameters:

    1. the element we want to observe, and
    2. a config object that defines what we want to observe through three boolean properties: attributes, childList, and subtree. In this case, we just want to check for changes in the child list, so we set that one to true:
    const observer = new MutationObserver(updateCustomProperties);
    const config = {attributes: false, childList: true, subtree: false};
    observer.observe(elements, config);

    That would be all we need! Using this method we can count many elements, in the following demo I set the max to 100, but it can easily reach tenfold:

    CodePen Embed Fallback

    So yeah, that’s our flamethrower right there. It definitely gets the fire started, but it’s plenty overkill for the vast majority of use cases. But that’s what we have while we wait for the perfect lighter.

    More information and tutorials

    • Possible Future CSS: Tree-Counting Functions and Random Values (Roman Komarov)
    • View Transitions Staggering (Chris Coyier)
    • Element Indexes (Chris Coyier)

    Related Issues

    • Enable the use of counter() inside calc() #1026
    • Proposal: add sibling-count() and sibling-index() #4559
    • Extend sibling-index() and sibling-count() with a selector argument #9572
    • Proposal: children-count() function #11068
    • Proposal: descendant-count() function #11069

    How to Wait for the sibling-count() and sibling-index() Functions originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    Source: Read More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleBest Free and Open Source Alternatives to Cisco Modeling Labs
    Next Article U++ – rapid application development framework

    Related Posts

    News & Updates

    How Red Hat just quietly, radically transformed enterprise server Linux

    June 2, 2025
    News & Updates

    OpenAI wants ChatGPT to be your ‘super assistant’ – what that means

    June 2, 2025
    Leave A Reply Cancel Reply

    Continue Reading

    Perficient Colleagues Donate 11,000+ Pounds of Food and 46,000+ Meals During Hunger Action Month

    Development

    pxtone collab is a sample-based music editor

    Linux

    This 5-in-1 charging station replaced several desk accessories for me (and it’s 33% off for Black Friday)

    Development

    Unable to navigate to Reqnroll steps contained in NuGet package using F12

    Development

    Highlights

    CVE-2024-13929 – ASPECT Servlet Injection Remote Code Execution Vulnerability

    May 22, 2025

    CVE ID : CVE-2024-13929

    Published : May 22, 2025, 6:15 p.m. | 36 minutes ago

    Description : Servlet injection vulnerabilities in ASPECT allow remote code execution if session administrator credentials become compromised.
    This issue affects ASPECT-Enterprise: through 3.08.03; NEXUS Series: through 3.08.03; MATRIX Series: through 3.08.03.

    Severity: 7.2 | HIGH

    Visit the link for more details, such as CVSS details, affected products, timeline, and more…

    The Flying Vessel

    August 7, 2024

    Microsoft Edge tests revamped PDF Viewer, new Password Manager and bottom Address Bar on Android

    December 20, 2024

    Microsoft Teams to block screen capture during meetings starting July 2025

    May 13, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.