Blog

Dynamic nth-child Selectors Without the Selector Rewrite

Replacing a fixed :nth-child() argument with a value that can change at runtime sounds redundant until the selector itself needs to respond to application state. If the highlighted item moves from the fifth child to the eighth, a conventional rule such as div:nth-child(5) no longer matches the intended element.

There is a CSS-based approach that separates the selection logic from the selector. Custom properties hold the adjustable values, sibling-index() reports an element’s position, and the new if() syntax inside style() turns those values into conditional styles.

The result does not make CSS variables usable directly inside :nth-child(). Instead, it recreates several familiar nth-child patterns through conditions. That distinction matters: this is a conditional styling technique, not a conventional selector whose arguments have become variables.

How the dynamic behavior works

The implementation uses a container with custom properties and a group of child elements. The children evaluate their own position rather than relying on a selector that names them directly.

For an exact position, the condition compares sibling-index() with a custom property:

“`css

.container { –B: 5; }

.container div {

Background: if(style(sibling-index() = –B): red; else: lightblue);

}

“`

When –B remains 5, the fifth child receives the first background. Changing –B changes which child satisfies the condition. No rewrite of the child selector is required.

The conditional value can also be applied to other properties:

“`css

.container div {

Background: if(style(sibling-index() = –B): red; else: lightblue);

Color: if(style(sibling-index() = –B): white);

}

“`

That last point reveals one of the technique’s trade-offs. Every property that needs the same selection logic may need its own conditional declaration. The approach is adjustable, but it is not always as compact as a static selector.

Selector patterns and their CSS equivalents

The same core mechanism can reproduce several :nth-child() forms. Each row below represents a selection condition built from sibling-index(), style(), and a custom property.

Selector pattern Conditional comparison Result
:nth-child(B) sibling-index() = –B The child at position B
:nth-child(n + B) sibling-index() >= –B The child at B and those after it
:nth-child(-n + B) sibling-index() <= –B The child at B and those before it
:nth-child(An) mod(sibling-index(), var(–A)) = 0 Children whose positions are multiples of A
Range from X to Y –X <= sibling-index() <= –Y Children within the selected range

The first three cases are direct translations of the corresponding selector arguments. The range combines the directional conditions, while the An case uses mod() to identify positions divisible by A.

Two special values make the modular pattern easier to interpret. When –A is 1, it corresponds to :nth-child(n), which selects every child. When –A is 2, it corresponds to :nth-child(2n), also expressible as :nth-child(even).

There is an important limitation with negative values for A. A negative multiple should select nothing because sibling positions are positive. The demonstrated implementation does not behave that way: mod() can still return zero for some negative values, causing elements to be selected incorrectly.

That means the technique should not be treated as a complete, general replacement for the browser’s :nth-child() syntax. It covers useful cases, but the modular implementation has an edge case that needs attention before negative values are trusted.

Why it matters

The practical benefit is not simply “CSS can replace a selector.” It is that selector arguments can become application-controlled data. The stylesheet defines how a position is evaluated, while a custom property supplies the value that determines which elements qualify.

That separation can help when a visual pattern follows changing data. Instead of creating or editing a selector for each possible position, a component can update one property. The value might represent an active child, the beginning of a range, or the interval used to identify repeated items.

Developers benefit most when the same condition drives several visual decisions and those decisions must remain synchronized. The technique also turns an experimental use of sibling-index(), style(), and if() into a concrete pattern rather than treating each feature in isolation.

The cost is clarity. A compact selector such as :nth-child(-n + 5) communicates its intent directly, while a conditional declaration exposes more of the underlying calculation. Repeated if() expressions can make a stylesheet harder to scan, especially when several properties share the same test.

Static :nth-child() remains the better choice when its arguments do not change. The dynamic approach earns its complexity when a value genuinely varies and updating the selector by hand would be inconvenient or repetitive.

Choosing where the technique fits

The strongest candidates are interfaces whose selected position or interval changes. The weakest are fixed layouts where the argument is known in advance. Adding conditional style logic to a static rule would add maintenance work without delivering a meaningful advantage.

The comparison also makes implementation details visible. Exact positions need one equality condition. A range needs two boundaries. Repetition needs a modulo test, along with careful handling of negative values. Selecting the simplest supported pattern is better than forcing every use case into the modular version.

The technique can apply to any CSS property, not only background color. However, each additional conditional property repeats the selection test. That makes the shared custom property valuable: it keeps the adjustable value in one place even when the declarations around it remain separate.

There is also a learning benefit. Building the pattern shows how style queries, sibling-index(), if(), and mod() can work together. The technique may still be unnecessary in a particular project, but the mechanics can transfer to other conditional styling problems.

What to watch next

Watch for clearer ways to express shared conditions and for improvements to the negative-value edge case. The broader opportunity is CSS that reacts to changing values without requiring developers to rebuild static rules for every state.

Use the pattern narrowly. Start with an exact or bounded position, keep the adjustable value in a custom property, and compare the result with the simpler static selector. If the conditional declarations become harder to maintain than the original rule, the original rule may still be the right tool.

Key takeaways

  • Custom properties cannot be used directly in the demonstrated :nth-child() form; the technique evaluates them through style() and sibling-index().
  • Equality, directional comparisons, ranges, and modular selections can all be reproduced with conditional styles.
  • The approach is most useful when a selector argument genuinely changes and least useful for fixed layouts.
  • Conditional declarations can become repetitive, so readability should be weighed against adjustability.
  • Negative A values expose a limitation in the modular implementation and require particular care.

Frequently asked questions

Can CSS variables now be used directly inside :nth-child()?

Not in the form demonstrated. The implementation keeps the selector logic separate and evaluates custom properties with style(), sibling-index(), and if().

What does sibling-index() represent?

It provides an element’s position among its siblings, allowing a style condition to compare that position with a custom property.

Can the technique select a range of children?

Yes. It combines a lower-bound condition with an upper-bound condition to select children within a configurable range.

What happens when A is negative?

The implementation can incorrectly select some elements because mod() may still return zero for negative values, even though a negative nth-child multiple should select nothing.

Source & credits. This article was originally published by css-tricks.com. All images belong to the original publisher. Content and images remain © their respective owners and are shared here for informational, non-commercial purposes with full attribution.

Related