Loops and Conditionals
Evidence lets you programmatically control what content appears on your pages using loops and conditional statements. This enables dynamic, data-driven reports that adapt to your data.
Each Loops
Loops let you iterate over query results and create repeating content for each row.
Basic Loop Syntax
{#each query_name as alias}
{alias.column_name}
{/each}
query_name: The query result to loop over
alias: A name you choose to reference the current row
- Access columns with
{alias.column_name}
Simple Loop Example
```sql top_products
SELECT
product_name,
sales,
units_sold
FROM products
ORDER BY sales DESC
LIMIT 5
```
## Top 5 Products
{#each top_products as product}
### {product.product_name}
- Sales: <Value data={product} column="sales" fmt="usd"/>
- Units Sold: <Value data={product} column="units_sold" fmt="num0"/>
{/each}
Output:
Top 5 Products
Laptop
- Sales: $45,000
- Units Sold: 150
Monitor
- Sales: $32,000
- Units Sold: 320
…
Loops with Components
Create repeated visualizations:
```sql cities
SELECT DISTINCT city
FROM sales
ORDER BY city
```
{#each cities as location}
## Sales in {location.city}
```sql city_sales_{location.city}
SELECT
DATE_TRUNC('month', date) as month,
SUM(revenue) as revenue
FROM sales
WHERE city = '${location.city}'
GROUP BY month
ORDER BY month
```
<LineChart
data={city_sales_{location.city}}
x="month"
y="revenue"
/>
{/each}
Query names inside loops must be unique. The example above would fail because each iteration creates the same query name. Use templated pages instead for this use case.
Better Alternative: Loops for Lists
```sql city_summary
SELECT
city,
SUM(sales) as total_sales,
COUNT(*) as order_count
FROM sales
GROUP BY city
ORDER BY total_sales DESC
```
{#each city_summary as city}
- **{city.city}**: <Value data={city} column="total_sales" fmt="usd"/> from <Value data={city} column="order_count"/> orders
{/each}
Loops in Components
Generate component children dynamically:
```sql categories
SELECT DISTINCT category
FROM orders
ORDER BY category
```
<Accordion>
{#each categories as cat}
<AccordionItem title={cat.category}>
Content for the {cat.category} category.
</AccordionItem>
{/each}
</Accordion>
Conditionals (If/Else)
Conditionals control what content displays based on data conditions.
Basic Conditional Syntax
{#if condition}
Content when condition is true
{:else}
Content when condition is false
{/if}
Simple Conditional Example
```sql sales_target
SELECT
100000 as target,
125000 as actual
```
{#if sales_target[0].actual >= sales_target[0].target}
## Target Achieved! 🎉
Congratulations! We exceeded our sales target.
{:else}
## Below Target
We need to increase sales to meet our target.
{/if}
Multiple Conditions
{#if condition_1}
First condition is true
{:else if condition_2}
Second condition is true
{:else}
No conditions are true
{/if}
Practical Example: Empty State Handling
```sql failed_orders
SELECT *
FROM orders
WHERE status = 'failed'
```
{#if failed_orders.length > 0}
## Failed Orders
<Warning>
There are {failed_orders.length} failed orders that need attention.
</Warning>
<DataTable data={failed_orders} />
{:else}
<Info>
No failed orders - everything is running smoothly!
</Info>
{/if}
Conditional Alerts Based on Thresholds
```sql performance_metrics
SELECT
0.15 as error_rate,
0.05 as warning_threshold,
0.10 as critical_threshold
```
{#if performance_metrics[0].error_rate > performance_metrics[0].critical_threshold}
<Alert status=error>
Critical: Error rate is at {performance_metrics[0].error_rate}%
</Alert>
{:else if performance_metrics[0].error_rate > performance_metrics[0].warning_threshold}
<Alert status=warning>
Warning: Error rate is elevated at {performance_metrics[0].error_rate}%
</Alert>
{:else}
<Alert status=success>
System performing normally. Error rate: {performance_metrics[0].error_rate}%
</Alert>
{/if}
Conditional Components
```sql metrics
SELECT
COUNT(*) as total_records
FROM large_dataset
```
{#if metrics[0].total_records > 1000}
<Warning>
Dataset is large. Showing summary statistics instead of full table.
</Warning>
```sql summary
SELECT
AVG(value) as avg_value,
MAX(value) as max_value,
MIN(value) as min_value
FROM large_dataset
```
<DataTable data={summary} />
{:else}
<DataTable data={large_dataset} />
{/if}
Combining Loops and Conditionals
Use loops and conditionals together for complex logic:
```sql team_performance
SELECT
team_name,
sales,
quota,
(sales::float / quota) as attainment
FROM teams
ORDER BY attainment DESC
```
{#each team_performance as team}
## {team.team_name}
{#if team.attainment >= 1.0}
✅ **Exceeding quota**: <Value data={team} column="attainment" fmt="pct"/> attainment
{:else if team.attainment >= 0.8}
⚠️ **On track**: <Value data={team} column="attainment" fmt="pct"/> attainment
{:else}
❌ **Below target**: <Value data={team} column="attainment" fmt="pct"/> attainment
{/if}
---
{/each}
Conditional Table Columns
Show different columns based on user input:
```sql summary
SELECT
category,
SUM(sales) as sales,
COUNT(*) as orders,
AVG(sales) as avg_order_value
FROM orders
GROUP BY category
```
<Dropdown name=metric>
<DropdownOption value="sales">Sales</DropdownOption>
<DropdownOption value="orders">Orders</DropdownOption>
<DropdownOption value="aov">Avg Order Value</DropdownOption>
</Dropdown>
<DataTable data={summary}>
<Column id="category" />
{#if inputs.metric.value === 'sales'}
<Column id="sales" fmt="usd0k" />
{:else if inputs.metric.value === 'orders'}
<Column id="orders" fmt="num0" />
{:else}
<Column id="avg_order_value" fmt="usd2" />
{/if}
</DataTable>
Common Conditional Patterns
Check Query Length
{#if query_name.length === 0}
No data available
{:else}
<DataTable data={query_name} />
{/if}
Compare Values
{#if current_value > previous_value}
📈 Trending up
{:else}
📉 Trending down
{/if}
Check for Null/Undefined
{#if data[0]?.value}
<Value data={data} column="value" />
{:else}
No value available
{/if}
Date Comparisons
{#if new Date(last_update[0].date) < new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)}
⚠️ Data is more than a week old
{/if}
JavaScript Expressions
Evidence supports JavaScript expressions inside { }:
{2 + 2}
<!-- Output: 4 -->
{orders.length} orders
<!-- Output: 42 orders -->
{(revenue[0].total / 1000).toFixed(1)}k
<!-- Output: 125.5k -->
{new Date().toLocaleDateString()}
<!-- Output: 3/4/2026 -->
When to Use Loops vs. Templated Pages
Use Loops when:
- Creating a list or small number of repeated sections on one page
- You don’t need unique URLs for each item
- Content is simple (headings, values, small charts)
Use Templated Pages when:
- Each item deserves its own page with a unique URL
- You have many items (>20)
- Each item has substantial content
- You want shareable links to individual items
For large datasets, prefer DataTables with search/filter instead of loops to maintain performance.
Loops execute on every row. For large datasets (1000+ rows), avoid:
- Creating charts in loops
- Running queries inside loops
- Complex calculations per row
Instead, use DataTables or pre-aggregate data in SQL.
Debugging Tips
View Query Data
Inspect what data is available:
{JSON.stringify(query_name, null, 2)}
Check Array Length
Query returned {query_name.length} rows
Conditional Debugging
{#if query_name.length === 0}
⚠️ Debug: Query returned no results
{/if}