Few CSS selectors are as familiar as :nth-child(). Striping a table, tidying the spacing in a product grid, highlighting the first few entries in a list: it handles all of them. Its one weakness is that the formula is baked into the selector itself. Want every fourth card instead of every third? You have to go back and edit the rule by hand, because custom properties can’t be dropped into a selector.
A new pairing of CSS features changes that. By combining sibling-index() with the inline if() function, it’s possible to recreate the behaviour of :nth-child() inside a property value, where variables are perfectly welcome. It’s more of an experiment than an everyday technique, but it’s a neat way to get to grips with features that will matter a great deal over the next few years.
In brief:
sibling-index()returns an element’s position among its siblings, counting from 1.if()lets a property choose a value based on a condition written insidestyle().- Together, they let custom properties decide which items are picked, instead of a hard-coded selector.
- Browser support is still limited, so treat this as progressive enhancement.

Picking Out One Item, or a Run of Them
The building block is sibling-index(). Ask it about any element and it hands back that element’s position, so the third list item gets 3. Compare that number with a custom property inside if() and you have a “selector” you can change from the outside.
/* The traditional way: fixed in the selector */
.steps > li:nth-child(4) {
outline: 3px solid crimson;
}
/* The adjustable way: the number lives in a variable */
.steps { --pick: 4; }
.steps > li {
outline: if(style(sibling-index() = --pick): 3px solid crimson);
}
Change --pick on the parent (in a media query, a theme class or with a dash of JavaScript) and a different item lights up. When the condition is false the declaration simply does nothing, and you can add an else branch if you want a fallback value instead.
Comparison operators make ranges just as easy. Using >= picks everything from a given position onwards, the equivalent of n + B. Using <= picks everything up to it, like -n + B. Put both together and you get a window:
.steps { --from: 3; --to: 6; }
.steps > li {
background: if(style(--from <= sibling-index() <= --to): gold; else: white);
}
That arguably reads more clearly than the traditional approach, which chains two :nth-child() selectors together and takes a moment to decode.
Every Third, Every Fourth and the Full An + B Formula
Repeating patterns need a little maths. An item sits on a multiple of N when dividing its position by N leaves nothing over, and CSS now has a mod() function to work out that remainder.
.gallery { --every: 4; }
.gallery > * {
border-color: if(
style((mod(sibling-index(), var(--every)) = 0) and (var(--every) > 0)): tomato;
else: transparent
);
}
The second check, that the step is above zero, is there for a reason. mod() happily works with negative numbers, so without it a negative step would still match some items, whereas the real :nth-child() would match none. Conditions inside style() can be joined with and or or, much like any programming language.
The general An + B form follows the same logic. Subtract the offset from the item’s position; the item qualifies if what’s left divides evenly by the step and dividing it gives a result of zero or more. A step of zero is the awkward case, because mod() with a zero divisor produces NaN and the comparison falls apart. It can be worked around with a nested if() and the sign() function, but most real projects will simply stick to non-zero values.

Common Mistakes to Avoid
- Assuming it works everywhere. At the time of writing,
if()andsibling-index()are mainly available in Chromium-based browsers. Always provide a sensible default for browsers that ignore the declaration. - Replacing every
:nth-child(). If the pattern never changes, the plain selector is shorter, easier to read and supported everywhere. - Repeating the same condition on lots of properties. Each property needs its own
if(), which quickly gets noisy. Working the result out once into a custom property and testing that keeps things tidier. - Ignoring edge cases. Negative steps and a step of zero behave differently from the native selector unless you guard against them.
- Counting from zero. Like
:nth-child(),sibling-index()starts at 1, so the first item is 1, not 0.
For most business websites, the native selector will stay the right tool for some time yet. What this experiment really shows is where CSS is heading: logic that once needed JavaScript, such as reacting to settings, switching patterns and choosing between values, is moving into the stylesheet itself. That means lighter pages and fewer moving parts to maintain. Which part of your own site would you make adjustable first?










