Alternating styles for nested elements

| permalink | css

In CSS, the :nth-child() selector allows us to apply alternating styles for sibling elements without adding classes to the elements, usually for zebra striping table rows.

However, doing the same for nested elements proved elusive.

I encountered this problem a few days ago with nested blockquotes and I asked myself, can I solve this problem without resorting to adding classes or explicitly defining each nesting level?

It turns out that yes, it's possible through two different methods.

Variable juggling

The first method, and the one with cross-browser support right now, makes use of intermediary elements in combination with the space toggle technique to essentially juggle the variables in order to switch styles for each nesting level.

<blockquote> 1
  <div>
    <blockquote> 2
      <div>
        <blockquote> 3
          /* ... */
        </blockquote>
      </div>
    </blockquote>
  </div>
</blockquote>
:root {
  /* space toggle shortcuts */
  --on: initial;
  --off: /*!*/;

  /* initialize style */
  --1: var(--on);
  --2: var(--off);
  --3: var(--off);
}

blockquote {
  /* rotate styles and cache them */
  --_1: var(--3);
  --_2: var(--1);
  --_3: var(--2);

  margin: 0;
  padding: 1rem;

  /* set values for each style */
  background:
    var(--1, #b9e7fa)
    var(--2, #d6b7f7)
    var(--3, #f5deb8);

  border-left: .25rem solid
    var(--1, #91bbcc)
    var(--2, #b79ad6)
    var(--3, #ccaa72);
}

div {
  /* promote cached values */
  --1: var(--_1);
  --2: var(--_2);
  --3: var(--_3);
}

CodePen Demo

An obvious question to ask is if we're modifying the HTML anyways, what is the benefit over adding classes to the elements?

First, the HTML stays the same regardless of the number of styles you are cycling through, simplifying development and theme switching.

Secondly, for elements such as lists, no additional elements are needed since the intermediary elements already exist in the forms of <ul> and <ol>.

Container style queries

With container style queries, there is no need to modify the HTML at all.

We can cycle our styles by querying a variable value, and then modifying said value within the style query to match the value you're using for the next style query and so on.

<blockquote> 1
  <blockquote> 2
    <blockquote> 3
      /* ... */
    </blockquote>
  </blockquote>
</blockquote>
:root {
  /* initialize style */
  --set: 1;
}

blockquote {
  margin: 0;
  padding: 1rem;

  /* rotate through multiple styles */
  @container style(--set: 1) {
    background: #b9e7fa;
    border-left: 0.25rem solid #91bbcc;
    --set: 2;
  }

  @container style(--set: 2) {
    background: #d6b7f7;
    border-left: 0.25rem solid #b79ad6;
    --set: 3;
  }

  @container style(--set: 3) {
    background: #f5deb8;
    border-left: 0.25rem solid #ccaa72;
    --set: 1;
  }
}

CodePen Demo

The drawback for this method is the lack of browser support - Chrome and Edge only at this time.

In closing

While container style queries would be the ideal solution, the lackluster browser support means that the variable juggling technique is the only game in town for now.