skip to Main Content

Given a flat list of elements (must be flat), I would like to a find an ideal combination of CSS selectors which colors only the <a> elements which comes after a checked <input>(checkbox), but not those who comes after some next sibling input:

(The number of items per checkbox is dynamic)

Is there a way to refactor the CSS selectors?

I’ve tried combinations with :has or :is but cannot come up with the right combination.

Ideally I would like a single selector for coloring on the <a> items between the checked input and the input the comes after it in the DOM tree.

There is also a bug in the below situation (see row 4):

(It’s quite a pickle…)

enter image description here

* {
  float: left;
  user-select: none;
}

input {
  clear: left;
}

input:checked ~ a,
input:checked ~ a ~ input:not(:checked) ~ :checked ~ a{
  background: gold;
}

input:checked ~ a ~ input:not(:checked) ~ a {
  background: none;
}
<input type=checkbox checked>
<a>🍒</a>
<a>🍒</a>
<a>🍒</a>

<input type=checkbox>
<a>🍒</a>
<a>🍒</a>
<a>🍒</a>
<a>🍒</a>

<input type=checkbox checked>
<a>🍒</a>

<input type=checkbox>
<a>🍒</a>
<a>🍒</a>

👉 I’ve created a proposal for an :until pseudo-selector

2

Answers


  1. Complex CSS Solution

    This solution involves position, pseudo-elements, and :has(). This solution will work for an unknown number of <a>s and lines that pre-existed or dynamically added (see Example A).

    Example A

    Details are commented in example

    * {
      float: left;
      user-select: none;
    }
    
    body {
      overflow-x: hidden;
    }
    
    input {
      clear: left;
    }
    
    a {
      position: relative /* Allows ::after to reference <a> for
      positioning and stacking order */;
    }
    
    /*
    - Targets the last <a> of each line.
    - It's purpose is to mask any gold background that extends past 
      the last <a> of each line.
    - Content is "🍒" which is transparent and is used to mirror
      the dimensions of a <a>.
    - Pseudo-element is positioned to the outside right edge of the
      last <a> of each line.
    */
    a:has(+input)::after, 
    a:last-of-type::after {
      content: "🍒";
      position: absolute;
      z-index: -1;
      left: 100%;
      width: 96vw;
      color: transparent;
      background: white;
    }
    
    /*
    - Targets the first <a> of each line.
    - It's purpose is to add a gold background that extends close
      to the right edge of viewport for each line that's checked.
    - Content is "🍒" which is transparent and is used to mirror
      the dimensions of a <a>.
    - Pseudo-element is positioned to the inside left edge of the 
      first <a> of each line. 
    */
    input:checked+a::after {
      content: "🍒";
      position: absolute;
      z-index: -2;
      left: 0;
      width: 96vw;
      color: transparent;
      background: gold;
    }
    
    /*
    Same as previous but width is adjusted for lines with a single
    <a>.
    */
    input:checked+a:has(+input)::after {
      width: 100%;
    }
    <input type="checkbox" checked><a>🍒</a><a>🍒</a><a>🍒</a>
    <input type="checkbox"><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a>
    <input type="checkbox" checked><a>🍒</a>
    <input type="checkbox"><a>🍒</a><a>🍒</a>
    <input type="checkbox" checked><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a>
    <input type="checkbox"><a>🍒</a><a>🍒</a><a>🍒</a><a>🍒</a>
    <input type="checkbox" checked><a>🍒</a>
    <input type="checkbox"><a>🍒</a><a>🍒</a>

    Simple HTML Solution

    If you’re looking for the most simplest of selectors, you’ll need to isolate each group in the layout with HTML. Wrap each group in a <label> and assign clear: left to each label. The last ruleset isn’t needed (see Example B).

    Example B

    * {
      float: left;
      user-select: none;
    }
    
    label {
      clear: left;
    }
    
    input:checked~a {
      background: gold;
    }
    <label>
      <input type="checkbox" checked>
      <a>🍒</a>
      <a>🍒</a>
      <a>🍒</a>
    </label>
    
    <label>
      <input type="checkbox">
      <a>🍒</a>
      <a>🍒</a>
      <a>🍒</a>
      <a>🍒</a>
    </label>
    
    <label>
      <input type="checkbox" checked>
      <a>🍒</a>
    </label>
    
    <label>
      <input type="checkbox">
      <a>🍒</a>
      <a>🍒</a>
    </label>

    Login or Signup to reply.
  2. The only solution I can actually see is a "pyramid" selector using + until the max number of elements you know you will have.

    * {
      float: left;
      user-select: none;
    }
    
    input {
      clear: left;
    }
    
    input:checked + a,
    input:checked + a + a,
    input:checked + a + a + a,
    input:checked + a + a + a + a,
    input:checked + a + a + a + a + a,
    input:checked + a + a + a + a + a + a,
    input:checked + a + a + a + a + a + a + a {
      background: gold;
    }
    <input type=checkbox checked>
    <a>🍒</a>
    <a>🍒</a>
    <a>🍒</a>
    
    <input type=checkbox>
    <a>🍒</a>
    <a>🍒</a>
    <a>🍒</a>
    <a>🍒</a>
    
    <input type=checkbox checked>
    <a>🍒</a>
    
    <input type=checkbox>
    <a>🍒</a>
    <a>🍒</a>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search