Search code examples
csslistcss-selectors

How do you select the nth item in a list based on that item’s child's attributes using only CSS selectors


So if I have a list like this:

<ul>
 <li><button disabled/></li>
 <li><button disabled/></li>
 <li><button /></li>
 <li><button /></li>
 <li><button /></li>
</ul>

And the amount of disabled buttons is never known - but will always be consecutive and appear before the enabled buttons, how do I select the second enabled button?

Right now I have:

  • “ul li:nth-of-type(2) button:not([disabled])”

but this only selects the second <li> with a child button that is not disabled - if it is disabled, it doesn’t select anything. In other words it doesn’t ‘find’ the second enabled button, it just checks if the second button is enabled or not and selects it accordingly.

I’ve tried

  • “ul li button:not([disabled]):nth-of-type(2)”

but the problem with that is the button is the only child of <li>, so nth-of-type(2) doesn’t have anything to select.

What I think I need to be able to write is something like this:

  • “ul (li button:not([disabled])):nth-of-type(2)”

but I don’t think that’s the correct syntax :(

Any help would be greatly appreciated! If it helps anyone, I'm writing a UDF test to find (and select) the second available time slot for a time picker component in React.


Solution

  • Here's a way you can do it using :has() which now looks to have support in all updated browsers (thanks FF for catching up):

    button {
      width: 100px;
      height: 50px;
    }
    
    
    /* Second */
    
    li:has(button[disabled])+li:has(button:not([disabled]))+li:has(button:not([disabled])) button {
      background: red;
    }
    
    
    /* first */
    
    li:has(button[disabled])+li:has(button:not([disabled])) button {
      background: blue;
    }
    
    
    /* third */
    
    li:has(button[disabled])+li:has(button:not([disabled]))+li:has(button:not([disabled]))+li:has(button:not([disabled])) button {
      background: green;
    }
    
    
    /* no disabled buttons per comment by haolt */
    
    li:first-child:has(button:not([disabled]))+li:has(button:not([disabled])) button {
      background: purple;
    }
    <ul>
      <li><button disabled/></li>
      <li><button disabled/></li>
      <li><button /></li>
      <li><button /></li>
      <li><button /></li>
    </ul>
    
    <ul>
      <li><button /></li>
      <li><button /></li>
      <li><button /></li>
      <li><button /></li>
      <li><button /></li>
    </ul>

    It's not very elegant, as we are chaining next sibling + selectors, but it works.