Executive Overview
In the ever-evolving landscape of web development, Cascading Style Sheets (CSS) have matured from a simple design-presentation language into a robust, programmable environment. Modern specifications now incorporate logical functions, mathematical operations, and style-driven conditional rendering that rival traditional programming languages. Among these paradigm-shifting additions are the emerging if() inline conditional function, element-aware structural pseudo-functions like sibling-index(), and advanced container style queries.
While static structural pseudo-classes such as :nth-child() have long been staples of front-end development, they suffer from a rigid design limitation: their arguments are immutable. A developer cannot bind custom CSS variables—such as --A or --B—directly into a traditional :nth-child(An + B) expression. Modifying structural patterns dynamically on the fly has traditionally required JavaScript intervention or a manual rewrite of stylesheets.
This technical deep-dive investigates an unconventional, highly experimental front-end engineering challenge: completely reimplementing the native :nth-child() feature using modern CSS style queries, the if() function, and math modules. By examining why this concept matters, breaking down step-by-step logic gates from simple indices to complex equations, and examining edge cases involving negative numbers and zero values, developers can master cutting-edge CSS paradigms that expand the boundaries of declarative styling.
Detailed Chronology and Technical Evolution: From Static Selectors to Dynamic Style Queries
The Core Problem: Immutability in Structural Selectors
To understand the motivation behind recreating :nth-child() programmatically, one must first evaluate the architectural limitations of native CSS syntax. Consider a standard structural rule designed to style every fifth element offset by eight:
div:nth-child(5n + 8)
background: red;
This declarative rule is evaluated by the browser engine during the style calculation phase. However, if an application requires the multiplier (5) or the offset (8) to shift dynamically based on user interaction, state changes, or container queries, native CSS fails. Developers cannot natively write:
.container
--A: 5;
--B: 8;
/* INVALID NATIVE CSS */
.container > div:nth-child(var(--A)*n + var(--B))
background: red;
The browser’s CSS parser expects the arguments inside :nth-child() to be integer constants (or the keyword n), prohibiting the direct insertion of custom properties (var(--A)). Consequently, achieving runtime flexibility traditionally forces developers to write verbose JavaScript to inject classes or recalculate inline styles.
Re-implementing :nth-child() purely in CSS via style queries and inline conditionals eliminates this friction, transforming a traditionally static selector into a fully variable-driven runtime engine. Furthermore, it serves as a rigorous pedagogical exercise for exploring upcoming CSS features.
The Foundational HTML and CSS Architecture
To establish a uniform testing ground for our implementations, we rely on a predictable DOM structure: a parent container housing an arbitrary series of child <div> elements.

<div class="container">
<div></div>
<div></div>
<div></div>
<!-- Additional child elements -->
</div>
The container controls the custom properties that act as parameters for our equations, while individual child elements evaluate their own index against those variables:
.container
--A: 5;
--B: 8;
.container > div
/* Dynamic evaluation based on custom properties */
background: /* conditional logic */;
Step-by-Step Implementation of Dynamic Selectors
1. Targeting a Single Element: :nth-child(B)
The simplest structural selection targets a single element by its absolute index, equivalent to div:nth-child(5). Here, B represents the precise index of the targeted sibling.
Utilizing the new if() syntax combined with container style queries and the sibling-index() function, we can express this condition cleanly:
.container
--B: 5;
.container div
background: if(style(sibling-index() = --B): red);
This syntax is fully valid under modern proposals. The if() function evaluates a condition defined within a style() wrapper. If the expression evaluates to true, the property adopts the specified value (red); otherwise, it falls back to its default state.
We can also introduce an else branch to handle non-matching elements, effectively replicating an if-else statement entirely within the stylesheet:
.container
--B: 5;
.container div
background: if(style(sibling-index() = --B): red; else: lightblue);
The structural layout of the if() syntax follows a clear template:
.container div
background:
if(style( condition ): <color_if_condition_true>;
else: <color_if_condition_false>);
Because style queries permit flexible parameter ordering, the condition can be written in multiple valid ways:
.container div
background: if(style(sibling-index() = --B): red);
background: if(style(sibling-index() = var(--B)): red);
background: if(style(var(--B) = sibling-index()): red);
background: if(style(--B = sibling-index()): red);
This flexibility extends across multiple CSS properties simultaneously, allowing comprehensive styling of matching nodes:

.container
--B: 5;
.container div
background: if(style(sibling-index() = --B): red; else: lightblue);
color: if(style(sibling-index() = --B): white);
2. Open-Ended Ranges: :nth-child(n + B) and :nth-child(-n + B)
Moving beyond isolated elements, we can construct range-based selections. To select every element from index B onward (equivalent to div:nth-child(n + 5)), we simply evaluate whether the sibling index is greater than or equal to B:
.container
--B: 5;
.container div
background: if(style(sibling-index() >= --B): red; else: lightblue);
Conversely, to select every element up to index B (equivalent to div:nth-child(-n + 5)), we invert the logical operator to less-than-or-equal:
.container
--B: 5;
.container div
background: if(style(sibling-index() <= --B): red; else: lightblue);
By combining these two boundary checks, developers can achieve precise range selection ([X, Y]) without chaining complex native pseudo-classes:
.container
--X: 3;
--Y: 8;
.container div
background: if(style(--X <= sibling-index() <= --Y): red);
This syntax is often more readable and intuitive than traditional native CSS pseudo-classes, which frequently require cognitive translation for complex intervals.
3. Periodic Selection: :nth-child(An)
Introducing the multiplier A—such as in div:nth-child(3n), which selects every third element (3rd, 6th, 9th, etc.)—requires checking whether the element’s index is an exact multiple of A.
This is accomplished using the mathematical mod() function. If the remainder of sibling-index() / A equals 0, the element is a multiple:
.container
--A: 3;
.container div
background: if(style(mod(sibling-index(), var(--A)) = 0): red);
Handling Negative Multipliers and Logical Conjunctions
While functional, this basic implementation contains a subtle edge case: negative values for A. Because all DOM sibling indices are strictly positive integers, a negative multiplier should logically select nothing. However, modulo arithmetic with negative numbers can inadvertently return 0, triggering false positives.
To ensure accuracy, we must validate the sign of A using compound conditions combined with the and keyword inside the style() query:

.container
--A: 3;
.container div
background: if(style((mod(sibling-index(), var(--A)) = 0) and (var(--A) > 0): red);
The if() function supports standard logical operators (and, or), allowing developers to construct robust evaluation trees comparable to traditional programming languages.
4. The Grand Unification: :nth-child(An + B)
Synthesizing all learned techniques yields the universal formula for :nth-child(). In this generic form, an element matches if its index satisfies the equation $An + B$, where $n$ is a non-negative integer ($n ge 0$).
We can isolate $n$ algebraically:
$$n = fractextindex – BA$$
For $n$ to be valid, two criteria must be met simultaneously:
- The division must yield a non-negative number:
$$fractextindex – BA ge 0$$ - The numerator must be evenly divisible by $A$ (i.e., its remainder must be zero):
$$textmod(textindex – B, A) = 0$$
Translating these mathematical requirements directly into CSS yields:
.container
--A: 3;
--B: 5;
.container div
background:
if(style(
(mod(sibling-index() - var(--B), var(--A)) = 0)
and (calc((sibling-index() - var(--B))/var(--A)) >= 0)
): red);
Defending Against Division by Zero
A critical limitation emerges when --A is set to 0. Division by zero results in a NaN (Not a Number) value within the mod() function, breaking the conditional evaluation engine.
To create a bulletproof implementation capable of handling --A: 0 gracefully, we can introduce intermediate custom properties that leverage the sign() math function:

.container
--A: 3;
--B: 5;
.container div
--n: calc((sibling-index() - var(--B))/var(--A));
--g: if(style(--A = 0):
calc(sign(sibling-index() - var(--B)) + 1);
else:
calc(sign(var(--n) + 1) + mod(var(--n), 1))
);
background: if(style(--g = 1): red);
In this advanced architecture, an intermediate indicator variable --g resolves to 1 strictly when the element matches the computed criteria. This pattern demonstrates the immense computational power now accessible directly within stylesheet runtimes.
Supporting Context, Metrics, and Feature Support
The practical application of these techniques relies heavily on modern browser parsing capabilities. Below is an overview of the core baseline features utilized in these implementations:
| Feature / Function | Description | Standardization Status |
|---|---|---|
sibling-index() |
Returns the 1-based index of an element among its siblings. | Emerging / Experimental |
if() inline conditional |
Allows inline conditional value resolution based on queries. | Proposed / Experimental |
| Container Style Queries | Queries parent custom properties and styles for branching logic. | Widely Supported in Modern Engines |
mod() and sign() |
Advanced math functions for remainder and sign determination. | Modern CSS Math Module Standard |
While these features push the absolute cutting edge of CSS capability, they highlight a pivotal shift: stylesheets are transitioning from static styling declarations into dynamic, reactive logic layers.
Future Outlook: The Horizon of Programmable CSS
Re-implementing :nth-child() using style queries and inline conditionals may seem like an academic exercise at first glance, but it provides profound insight into the future of web architecture.
As container queries, style queries, and advanced math functions (mod, round, sign, hypot) achieve universal browser interoperability, the line between layout styling and application logic will continue to blur. Developers will increasingly rely on native CSS variables to drive complex reactive UI states without incurring the performance overhead or bundle-size penalties of JavaScript-heavy DOM manipulation.
For developers seeking the next challenge, the logical extension of this architecture is tackling :nth-last-child(). By incorporating the sibling-count() function to evaluate indices from the end of a sibling group rather than the beginning, engineers can unlock fully bidirectional dynamic structural selection—cementing CSS as a truly programmable, reactive design engine.
