r-for Directive

Render reactive collections with stable keys that preserve each item’s identity.

r-for renders repeated template instances from arrays, iterables, and object-like values.

r-for + :keyControl flow

Render reactive collections with stable keys that preserve each item’s identity.

Try it live

A list with stable identityLive Regor
  • Design the interfacekey: design
  • Connect the statekey: build
  • Verify the behaviorkey: check

Reverse the order: stable keys let Regor reuse the existing row elements.

import { html, sref } from 'regor'

const initial = [
  { id: 'design', label: 'Design the interface' },
  { id: 'build', label: 'Connect the state' },
  { id: 'check', label: 'Verify the behavior' },
]

export function createFor(interactive = false) {
  const rows = sref(initial.map((row) => ({ ...row })))
  return {
    interactive,
    rows,
    reverse: () => rows([...rows()].reverse()),
    remove: (id: string) => rows(rows().filter((row) => row.id !== id)),
    reset: () => rows(initial.map((row) => ({ ...row }))),
  }
}

export const forTemplate = html` <div class="guide-demo">
  <div class="guide-session-bar">
    <button type="button" @click="reverse" :disabled="!interactive">
      Reverse order</button
    ><button type="button" @click="reset" :disabled="!interactive">
      Reset list
    </button>
  </div>
  <ul class="guide-service-list">
    <li r-for="row in rows" :key="row.id">
      <div>
        <strong>{{ row.label }}</strong><small>key: {{ row.id }}</small>
      </div>
      <button
        type="button"
        :aria-label="'Remove ' + row.label"
        @click="remove(row.id)"
        :disabled="!interactive"
      >
        Remove
      </button>
    </li>
  </ul>
  <p r-if="rows.length === 0" class="guide-empty">
    The list is empty. Reset it to start again.
  </p>
  <p class="guide-hint">
    Reverse the order: stable keys let Regor reuse the existing row elements.
  </p>
</div>`
import { createApp, useScope } from 'regor'
import { directiveExamples } from './examples'

const element = document.getElementById('directive-demo')
const name = element?.dataset.example
if (element && name && Object.hasOwn(directiveExamples, name)) {
  const example = directiveExamples[name as keyof typeof directiveExamples]
  createApp<object>(
    useScope<object>(() => example.create(true)),
    { element, template: example.template },
  )
}

Reverse the list or remove rows. Keys let surviving items keep their DOM elements.

Usage

<li r-for="item in items">{{ item }}</li>

You can use in or of:

<li r-for="item of items">{{ item }}</li>

Index Variable

Use #-prefixed variable for index:

<li r-for="(item, #i) in items">{{ i }} - {{ item }}</li>

Iterating Over Objects

Object iteration is supported:

<li r-for="(key, value) in person">{{ key }}: {{ value }}</li>

Object Destructuring

Destructuring is supported:

<li r-for="{ name, age }, #i in users">{{ i }} - {{ name }} ({{ age }})</li>

Complex Expressions

Any valid expression can provide iterable data:

<ul>
  <li r-for="n in numbers.filter((n) => n > 5)">{{ n }}</li>
</ul>

Keying (key and :key)

For stable identity and predictable updates, set a key on repeated nodes.

<li r-for="row in rows" :key="row.id" r-text="row.name"></li>

Important behavior in Regor templates:

  1. key="row.id" and :key="row.id" are both expression-based for list keying.
  2. Nested key paths are supported, including a.b.c.d.
  3. Use stable, unique keys from your domain model.

If keys are unstable, DOM reuse quality degrades and updates become less predictable.

Table Templates

r-for supports native table structures and component-based captions, sections, rows, cells, column groups, and columns.

<table>
  <tbody>
    <TableRow r-for="row in rows" :row="row" />
  </tbody>
</table>
const tableCell = defineComponent(html`<td><span>{{ value }}</span></td>`, {
  props: ['value'],
})

const tableRow = defineComponent(
  html`<tr>
    <TableCell :value="row.name" />
    <TableCell :value="row.age" />
  </tr>`,
  { props: ['row'] },
)
<table>
  <colgroup r-for="group in columnGroups">
    <col r-for="col in group.cols" :data-width="col.width" />
  </colgroup>
</table>

Best Practices

  1. Keep row templates minimal.
  2. Use stable keying.
  3. Avoid expensive per-row expressions in large lists.
  4. Prefer pagination/windowing for very large data sets.
Identity comes from the item

Use row.id when it is stable and unique. The row position changes when you reorder a list.

See Also

  1. r-if
  2. r-bind
  3. r-text
Keep exploring Add conditional empty states.