# $[index]: a different image or value for every element in one line

> Give every matched element its own value with $[index], even inside url(): slider images, staggered animation delays and color ramps in one CSS rule.

Source: https://csspp.dev/docs/index-variable/


`$[index]` is replaced with each matched element's position, counting from 0. One rule can give fifty slides fifty different background images, stagger an animation across a list, or rotate colors, without writing a rule per element.

```css
.slider > div { background-image: url(/img/slide-$[index].jpg); }
```

If the slider has four slides, they get `slide-0.jpg`, `slide-1.jpg`, `slide-2.jpg` and `slide-3.jpg`. Add a fifth slide in your HTML and it gets `slide-4.jpg`, with no CSS change.

## Why not native CSS?

Since 2026, native CSS has `sibling-index()` in all current browsers. It returns a number you can use in `calc()`, which covers some of the same ground. It cannot build a URL: `url()` does not accept functions or variables, so `url(/img/slide-sibling-index().jpg)` is not possible. `$[index]` is substituted as text, so it works anywhere: in `url()`, in `content`, in grid lines and in selectors' values.

| | `sibling-index()` | `$[index]` |
|---|---|---|
| Use inside `url()` and `content` | No | Yes |
| Counts | Position among siblings, from 1 | Order among all matched elements, from 0 by default |
| Works in older browsers | No | Yes |

## Examples

### Staggered animation

```css
.list > li {
  animation: rise .5s both;
  animation-delay: calc($[index] * 80ms);
}
@keyframes rise { from { opacity: 0; translate: 0 1rem; } }
```

### Color ramp

```css
.swatches > span { background: hsl(calc($[index] * 36) 80% 60%); }
```

### Start counting at 1

Pass `&self` and a starting number:

```css
.steps > li::before { content: "Step $[index(&self,1)]"; }
```

### Image sequences in several places

The index counts in document order across every element the selector matches. Two galleries matched by `.gallery img` share one sequence, `0` to `n`. To number each gallery from 0, give each its own selector:

```css
#summer img { background-image: url(/img/summer-$[index].jpg); }
#winter img { background-image: url(/img/winter-$[index].jpg); }
```

## More built-in values {#values}

| Value | Gives | Example |
|---|---|---|
| `$[count]` | How many elements the rule's selector matches, across the whole page | `.cols > div { width: calc(100% / $[count]); }` |
| `$[random(1,5)]` | A random number per element, kept for as long as the page is open. Whole numbers when both limits are whole. | `.hero { background-image: url(/img/hero-$[random(1,5)].jpg); }` |
| `$[attr(data-bg)]` | The element's own attribute value, as text | `.card { background-image: url($[attr(data-bg)]); }` |
| `$[year]` | The current year | `footer::after { content: "© $[year]"; }` |

Like `$[index]`, they work in any property, including `url()` and `content`, and they are recalculated after the page loads and when the window resizes. Each element keeps its own `$[random()]` value. Native CSS can't do this: `url()` won't accept `attr()` or `random()`, and native `random()` is only in Safari and Chrome 156+.

## How it works

css-pp finds the elements that match the rule's selector, gives each a stable `data-css-i` attribute and writes one rule per element:

```css Output
.slider > div[data-css-i="1"]{background-image:url(/img/slide-0.jpg)}
.slider > div[data-css-i="2"]{background-image:url(/img/slide-1.jpg)}
.slider > div[data-css-i="3"]{background-image:url(/img/slide-2.jpg)}
```

This is accurate however the markup is arranged: a heading before the slides, slides wrapped in `<figure>` elements, several containers, or a selector list. The attribute selector has the same specificity as `:nth-child()`, so your cascade doesn't change. Indexes are recalculated after the page loads and when the window is resized. That matters when the stylesheet is opened in `<head>`, before the elements exist.

> **Version note.** Before 3.0, `$[index]` was mapped to `:nth-child()` positions. That gave wrong results when the matched elements weren't consecutive children of a single parent. 3.0 uses the stable attributes described above.
