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:

CSS — the hard way
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:

HTML
<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

HTML + FSCSS (v1.2.1)
<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.

Pattern
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:

FSCSS
@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

Compiled CSS (4 levels)
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

  1. @arr colors[…] holds the values you want per level.
  2. @arr levels[count(@arr.colors!.length)] builds 1, 2, 3, … matching the array size (v1.2.1+). Older style: count(4, 1).
  3. empty{ /* preserve */ } keeps loop side-effects from becoming extra selectors.
  4. 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 ", …
  5. Inside the block, @arr.colors[@arr.levels[]] picks the color for the current index (1-based in this pattern).
  6. 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:

FSCSS
@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:

Generated
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:

FSCSS
@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

FSCSS
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.:

FSCSS
@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

FSCSS
@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:

1
2
3
4

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.