Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Template-based list rendering with safe data binding. Fetch or inline JSON, bind to templates, and render lists with grid, stack, or reel layouts.
The <card-list> component renders a list of items from JSON data using an HTML <template>. Data binding uses data-field attributes — only safe property paths are allowed, no expressions or method calls.
Supply data inline via data-items or fetch from a URL via src. Choose a layout with data-layout: grid, stack, or reel.
<card-list data-layout="grid" data-items='[ {"id": 1, "name": "Headphones", "price": "$249", "inStock": true}, {"id": 2, "name": "Keyboard", "price": "$159", "inStock": true}, {"id": 3, "name": "Monitor", "price": "$449", "inStock": false}]' data-key="id"> <template> <layout-card> <header><h3 data-field="name"></h3></header> <section> <p data-field="price"></p> </section> <footer> <button data-field-if="inStock">Add to Cart</button> <span data-field-unless="inStock">Out of Stock</span> </footer> </layout-card> </template></card-list>
| Attribute | Type | Default | Description |
|---|---|---|---|
src |
URL | — | URL to fetch JSON data from. Expects an array or an object with items or data property. |
data-items |
JSON string | — | Inline JSON array of items. Takes priority over src. |
data-key |
string | id |
Property name to use as the unique key for each item. |
data-layout |
string | block |
Layout mode: grid (auto-fit columns), stack (vertical), or reel (horizontal scroll). |
These go on elements inside the <template> to bind item data.
| Attribute | Description |
|---|---|
data-field="path" |
Set the element's text content from a property path (e.g. user.name). |
data-field-attr="href: url, title: name" |
Set one or more HTML attributes from property paths. Comma-separated pairs of attribute: path. |
data-field-html="path" |
Set innerHTML from a property path. Content is sanitized (scripts, iframes, and event handlers are stripped). |
data-field-if="path" |
Keep element only if the property value is truthy. Removed from DOM otherwise. |
data-field-unless="path" |
Keep element only if the property value is falsy. Removed from DOM otherwise. |
Pass a JSON array directly in the data-items attribute. Good for small, static datasets.
Set src to fetch JSON from a URL. The component shows a loading state while fetching and an error state on failure.
<card-list src="/api/products.json" data-layout="grid" data-key="id"> <template> <layout-card> <header><h3 data-field="name"></h3></header> <section><p data-field="description"></p></section> </layout-card> </template></card-list>
The response can be a plain array, or an object with an items or data property containing the array.
Access nested data with dot notation. Array indices are also supported: items[0].title.
<card-list data-layout="stack" data-items='[ {"id": 1, "user": {"name": "Alice", "email": "[email protected]"}}, {"id": 2, "user": {"name": "Bob", "email": "[email protected]"}}]' data-key="id"> <template> <layout-card data-variant="outlined"> <layout-cluster data-layout-justify="between" data-layout-align="center"> <strong data-field="user.name"></strong> <small data-field="user.email"></small> </layout-cluster> </layout-card> </template></card-list>
Use data-field-attr to set HTML attributes from item properties. Comma-separated pairs map attributes to property paths.
<card-list data-layout="grid" data-items='[ {"id": 1, "title": "Getting Started", "url": "#start", "desc": "Learn the basics."}, {"id": 2, "title": "API Reference", "url": "#api", "desc": "Full documentation."}]' data-key="id"> <template> <layout-card data-variant="outlined"> <h3><a data-field="title" data-field-attr="href: url, title: desc"></a></h3> <p data-field="desc"></p> </layout-card> </template></card-list>
Use data-field-if and data-field-unless to conditionally include or exclude elements based on item data. Elements are removed from the DOM, not hidden.
<card-list data-layout="grid" data-items='[ {"id": 1, "name": "Free", "price": "$0/mo", "featured": false}, {"id": 2, "name": "Pro", "price": "$29/mo", "featured": true}]' data-key="id"> <template> <layout-card> <header> <h3 data-field="name"></h3> <layout-badge data-field-if="featured">Popular</layout-badge> </header> <section><p data-field="price"></p></section> <footer> <button class="primary" data-field-if="featured">Get Started</button> <button data-field-unless="featured">Learn More</button> </footer> </layout-card> </template></card-list>
Use data-field-html to render HTML content from a property. Content is sanitized — <script>, <iframe>, <object>, <embed>, <form>, event handlers, and javascript: URIs are all stripped.
<card-list data-items='[ {"id": 1, "title": "Bold", "content": "<strong>Rich</strong> HTML content"}]' data-key="id"> <template> <article> <h3 data-field="title"></h3> <div data-field-html="content"></div> </article> </template></card-list>
| Value | Display | Description |
|---|---|---|
grid |
CSS Grid | Auto-fit columns with minmax(280px, 1fr). Responsive by default. |
stack |
Flex column | Vertical stack with gap. |
reel |
Flex row | Horizontal scroll with snap points. |
| Attribute | When | Visual |
|---|---|---|
data-loading |
During src fetch |
Reduced opacity with "Loading..." message. |
data-error |
Fetch failure | Error message banner with the HTTP status. |
| Event | Detail | Description |
|---|---|---|
card-list:rendered |
{ count: number } |
Fired after items are rendered. Bubbles. |
const list = document.querySelector('card-list'); list.addEventListener('card-list:rendered', (e) => { console.log(`Rendered ${e.detail.count} items`);});
| Method / Property | Description |
|---|---|
setItems(items) |
Replace the item list and re-render. |
getItems() |
Returns a shallow copy of the current items array. |
const list = document.querySelector('card-list'); // Set items programmaticallylist.setItems([ { id: 1, name: 'Item A' }, { id: 2, name: 'Item B' },]); // Read current itemsconst items = list.getItems(); // returns a copy/code-block
All data-field paths are validated against a strict regex. Only simple property access is allowed:
name, user.email, items[0].titleprice.toFixed(2), `${price}`, price * 1.1, stock > 0 ? 'yes' : 'no'Invalid paths log a console warning and resolve to empty content.
Without JavaScript, the <template> is hidden by default. Consider providing a <noscript> fallback or static content before the template for no-JS contexts.
<layout-card> — Card visual shell for list items<layout-grid> — CSS grid layout primitive<layout-stack> — Vertical stack layout<layout-reel> — Horizontal scroll layout<data-table> — For tabular data display