Styling Nested divs with One Loop
Generate div, div div, div div div… with a single @arr + rpt() pattern. No more hand-written selector chains.
This guide combines arrays & loops, @define, and rpt() from the core docs.
Examples use FSCSS v1.2.1 — including
count(@arr.name!.length), the empty{ /* preserve */ } pattern,
and inline helpers.
The problem
Writing nested selectors by hand is painful and error-prone:
div { background: orange; }
div div { background: purple; }
div div div { background: indigo; }
div div div div { background: blue; }
/* …and it gets worse the deeper you go */
You have a simple nested structure:
<div>1
<div>2
<div>3
<div>4</div>
</div>
</div>
</div>
You want each level to have a different background (and maybe padding, radius, etc.) without maintaining a growing list of selectors.
The one-loop solution
<script type="module" async>
import xfscss from "https://cdn.jsdelivr.net/npm/fscss@1.2.1/esm.min.js";
xfscss.reboot();
</script>
<style>
@arr colors[orange, purple, indigo, blue]
@arr levels[count(@arr.colors!.length)] /* → 1, 2, 3, 4 (v1.2.1+) */
empty{
/* preserve — keeps loop output from being treated as extra selectors */
}
rpt(@arr.levels[], "div ") {
background: @arr.colors[@arr.levels[]];
padding: 10px;
border-radius: 10px;
color: #fff;
margin: 8px;
}
</style>
<div>1
<div>2
<div>3
<div>4</div>
</div>
</div>
</div>
One colors array, one levels array driven by count(@arr.colors!.length),
one rpt() in selector position, and an empty{} guard.
Why empty{ /* preserve */ }?
When a loop expands, intermediate tokens can sometimes be interpreted as extra selectors.
The empty{ … } block acts as a preserve barrier: it absorbs
those tokens so they do not become stray rules.
empty{
/* preserve */
}
rpt(@arr.levels[], "div ") {
/* only this becomes real selectors */
}
Use it before (or between) loop-driven rules whenever you see unexpected selector output in the compiled CSS or console. Harmless empty rules are stripped or ignored downstream.
count(@arr.name!.length) — v1.2.1+
Before 1.2.1, count() needed a literal number
(count(3, 1)). From 1.2.1 you can drive it from an array’s length:
@arr colors[orange, purple, indigo, blue]
@arr levels[count(@arr.colors!.length)] /* length → 4 → levels 1..4 */
That keeps depth and the colors array in sync automatically — add or remove a color and the generated selector chain follows.
See the full method list on the Arrays page
(.length, .list, .indices, etc.).
What gets generated
div {
background: orange;
padding: 10px;
border-radius: 10px;
color: #fff;
margin: 8px;
}
div div {
background: purple;
padding: 10px;
border-radius: 10px;
color: #fff;
margin: 8px;
}
div div div {
background: indigo;
padding: 10px;
border-radius: 10px;
color: #fff;
margin: 8px;
}
div div div div {
background: blue;
padding: 10px;
border-radius: 10px;
color: #fff;
margin: 8px;
}
How it works
-
@arr colors[…]holds the values you want per level. -
@arr levels[count(@arr.colors!.length)]builds1, 2, 3, …matching the array size (v1.2.1+). Older style:count(4, 1). -
empty{ /* preserve */ }keeps loop side-effects from becoming extra selectors. -
rpt(@arr.levels[], "div ")sits in selector position. For each level it repeats the string"div "that many times:"div ","div div ","div div div ", … -
Inside the block,
@arr.colors[@arr.levels[]]picks the color for the current index (1-based in this pattern). - FSCSS expands the whole rule once per array item — one clean loop, pure CSS after compile.
The same selector-position + block rule that drives array loops in general is what makes the expansion reliable here.
@define helper for parallel property classes
Sometimes you also want utility classes that expose the same values as custom properties (or other properties). A small @define + array loop does that:
@define abc(arr){`
empty{
/* preserve */
}
@arr i-levels[count(@arr.@use(arr)!.length)]
.p@arr.@use(arr)[@arr.i-levels[]]{
--@arr.i-levels[]: @arr.@use(arr)[@arr.i-levels[]];
}
`}
@arr arr[RED, BLUE, GREEN]
exec(_log, "@abc(arr)")
Console / compiled output looks like:
empty{
/* preserve */
}
.pRED {
--1: RED;
}
.pBLUE {
--2: BLUE;
}
.pGREEN {
--3: GREEN;
}
This is the same preserve + length + loop idea, packaged as a reusable
@define. Call it with any array name. See also
inline helpers for related patterns.
Easy variations
Change depth (synced to array)
Add or remove colors — levels follow automatically:
@arr colors[tomato, coral, salmon, crimson]
@arr levels[count(@arr.colors!.length)]
empty{ /* preserve */ }
rpt(@arr.levels[], "div ") {
background: @arr.colors[@arr.levels[]];
padding: 10px;
border-radius: 10px;
}
Child combinator instead of descendant
rpt(@arr.levels[], "div > ") {
/* generates: div >, div > div >, div > div > div > */
background: @arr.colors[@arr.levels[]];
}
Different values per level
Reference the level index for anything — font-size, opacity, animation-delay, etc.:
@arr colors[orange, purple, indigo]
@arr sizes[1rem, 0.9rem, 0.8rem]
@arr levels[count(@arr.colors!.length)]
empty{ /* preserve */ }
rpt(@arr.levels[], "div ") {
background: @arr.colors[@arr.levels[]];
font-size: @arr.sizes[@arr.levels[]];
padding: 10px;
border-radius: 10px;
color: #fff;
}
Other tags or class chains
@arr levels[count(3, 1)]
empty{ /* preserve */ }
rpt(@arr.levels[], ".card ") {
/* .card , .card .card , .card .card .card */
border: 1px solid #334155;
padding: 12px;
}
Live demo
Four levels — orange → purple → indigo → blue — as produced by the one-loop pattern:
Drop the one-loop snippet into a page with the
1.2.1 runtime / esm build (or compile with the CLI) to generate the same result from source.
Why this feels good
- Zero repetitive typing — depth and styles stay in one place
- Depth and colors stay in sync —
count(@arr.colors!.length)does the bookkeeping - empty{} preserve — keeps loop output clean
- Still pure CSS after compilation — no runtime cost for the nested rules
- Works with browser runtime, esm, or CLI — same source either way
Hand-writing nested selectors used to be a small but annoying chore.
With FSCSS arrays + rpt() (and the 1.2.1 length + preserve helpers),
it becomes a reusable one-liner pattern.