Iteration Patterns
Common patterns for displaying array data as tables, lists, cards, and grids.
Basic Table
The most common pattern - display array data in a table:
Data:
{
"products": [
{ "name": "Widget", "sku": "WGT-001", "price": 29.99, "stock": 150 },
{ "name": "Gadget", "sku": "GDG-002", "price": 49.99, "stock": 75 },
{ "name": "Gizmo", "sku": "GZM-003", "price": 19.99, "stock": 200 }
]
}
Template:
<table>
<thead>
<tr>
<th>SKU</th>
<th>Product</th>
<th>Price</th>
<th>Stock</th>
</tr>
</thead>
<tbody>
{% for product in products %}
<tr>
<td>{{ product.sku }}</td>
<td>{{ product.name }}</td>
<td>{{ product.price | format_number: "C" }}</td>
<td>{{ product.stock }}</td>
</tr>
{% endfor %}
</tbody>
</table>
Table with Row Numbers
Add row numbers using forloop.index:
<tbody>
{% for item in items %}
<tr>
<td>{{ forloop.index }}</td>
<td>{{ item.name }}</td>
<td>{{ item.value }}</td>
</tr>
{% endfor %}
</tbody>
Table with Alternating Rows
Style odd/even rows differently:
{% for item in items %}
<tr class="{% if forloop.index | modulo: 2 == 0 %}even{% else %}odd{% endif %}">
<td>{{ item.name }}</td>
</tr>
{% endfor %}
CSS:
Table with Row Totals
Calculate and display totals per row:
{% for line in order.lines %}
<tr>
<td>{{ line.description }}</td>
<td>{{ line.quantity }}</td>
<td>{{ line.unitPrice | format_number: "C" }}</td>
<td>{{ line.quantity | times: line.unitPrice | format_number: "C" }}</td>
</tr>
{% endfor %}
Simple List
Unordered list:
Ordered list:
Definition List
For key-value pairs:
<dl>
{% for field in fields %}
<dt>{{ field.label }}</dt>
<dd>{{ field.value }}</dd>
{% endfor %}
</dl>
Comma-Separated List
Join items inline:
<p>Tags: {{ tags | join: ", " }}</p>
<!-- Or with more control -->
<p>Attendees:
{% for person in attendees %}
{{ person.name }}{% unless forloop.last %}, {% endunless %}
{% endfor %}
</p>
Card Grid
Display items as cards in a flexible grid:
Template:
<div class="card-grid">
{% for product in products %}
<div class="card">
<h3>{{ product.name }}</h3>
<p class="price">{{ product.price | format_number: "C" }}</p>
<p>{{ product.description }}</p>
</div>
{% endfor %}
</div>
CSS:
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.card {
border: 1px solid #ddd;
padding: 15px;
border-radius: 8px;
}
Two-Column Layout
Split items into two columns:
<div class="two-columns">
<div class="column">
{% for item in items limit: items.size | divided_by: 2 %}
<p>{{ item.name }}</p>
{% endfor %}
</div>
<div class="column">
{% for item in items offset: items.size | divided_by: 2 %}
<p>{{ item.name }}</p>
{% endfor %}
</div>
</div>
Nested Loops
Iterate over nested arrays:
Data:
{
"categories": [
{
"name": "Electronics",
"items": [
{ "name": "Phone", "price": 699 },
{ "name": "Laptop", "price": 1299 }
]
},
{
"name": "Books",
"items": [
{ "name": "Novel", "price": 15 },
{ "name": "Textbook", "price": 85 }
]
}
]
}
Template:
{% for category in categories %}
<h2>{{ category.name }}</h2>
<ul>
{% for item in category.items %}
<li>{{ item.name }} - {{ item.price | format_number: "C" }}</li>
{% endfor %}
</ul>
{% endfor %}
Grouped Items
Group items by a property:
Data:
{
"orders": [
{ "status": "pending", "id": "001", "total": 150 },
{ "status": "shipped", "id": "002", "total": 200 },
{ "status": "pending", "id": "003", "total": 75 },
{ "status": "delivered", "id": "004", "total": 300 }
]
}
Using repeat() with JSONPath filtering:
<h3>Pending Orders</h3>
{{ repeat("$.orders[?(@.status=='pending')]",
"<p>Order {{id}}: {{total | format_number: 'C'}}</p>",
"") }}
<h3>Shipped Orders</h3>
{{ repeat("$.orders[?(@.status=='shipped')]",
"<p>Order {{id}}: {{total | format_number: 'C'}}</p>",
"") }}
Empty State
Handle empty arrays gracefully:
{% for item in items %}
<div class="item">{{ item.name }}</div>
{% else %}
<div class="empty-state">
<p>No items to display.</p>
</div>
{% endfor %}
Limited Display
Show only a subset of items:
<!-- First 5 items -->
<h3>Top 5 Products</h3>
{% for product in products limit: 5 %}
<p>{{ product.name }}</p>
{% endfor %}
<!-- Show "and X more" -->
{% if products.size > 5 %}
<p class="more">and {{ products.size | minus: 5 }} more...</p>
{% endif %}
Pagination Pattern
For multi-page documents with many items:
{% for item in items %}
<div class="item">
{{ item.name }}
</div>
<!-- Page break every 10 items -->
{% assign remainder = forloop.index | modulo: 10 %}
{% if remainder == 0 and forloop.last == false %}
<div style="page-break-after: always;"></div>
{% endif %}
{% endfor %}
Summary Statistics
Show count and totals:
<p>Total items: {{ items.size }}</p>
{% assign total = 0 %}
{% for item in items %}
{% assign total = total | plus: item.price %}
{% endfor %}
<p>Total value: {{ total | format_number: "C" }}</p>
Conditional Row Styling
Highlight specific rows:
{% for order in orders %}
<tr class="{% if order.priority == 'high' %}highlight{% endif %} {% if order.overdue %}overdue{% endif %}">
<td>{{ order.id }}</td>
<td>{{ order.customer }}</td>
<td>{{ order.total | format_number: "C" }}</td>
</tr>
{% endfor %}
CSS:
See Also
- Data Binding - Array access and iteration
- repeat() Function - JSONPath filtering
- Invoice Template - Complete table example
- Conditional Content - Show/hide patterns