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.
WAI-ARIA Feed timeline of user actions with relative timestamps. Optional date grouping, optional infinite-scroll sentinel, optional avatar / icon left-rail.
<activity-feed> renders a vertical timeline of user actions ("Ada commented on issue #482, 2 hours ago"). Read-only and immutable per entry. Authors render entries as <article data-activity data-time=ISO> children; the component decorates each with a relative-time badge, optional date headings, and optional avatar / icon left-rail.
| Use this | When |
|---|---|
<activity-feed> | One-way, immutable timeline of "actor verb object" actions with relative timestamps. |
<comment-thread> | Threaded, mutable discussion with replies + reactions + edit/delete. |
<recently-visited> | Browser navigation history, scoped per-user / per-session. |
<time-index> | Changelog / version-filtered release timeline. |
<chat-thread> | Real-time chat with sender grouping (different interaction model). |
Each entry is a direct <article data-activity> child with a required data-time attribute (ISO-8601). Inside the entry, you author the content however you like — the component does not dictate the verb vocabulary or object shape.
<activity-feed aria-label="Recent activity"> <article data-activity data-time="2026-05-14T10:00:00Z"> <a href="/users/ada">Ada</a> commented on <a href="/issues/482">issue #482</a> </article> <article data-activity data-time="2026-05-14T09:30:00Z"> <a href="/users/bob">Bob</a> opened <a href="/issues/485">issue #485</a> </article></activity-feed>
Set data-group="day" or data-group="week" to insert date headings between entries. "Today" and "Yesterday" are localized; older days use the locale's date format.
<activity-feed data-group="day" aria-label="Recent activity"> <article data-activity data-time="2026-05-14T11:00:00Z">…</article> <article data-activity data-time="2026-05-14T09:15:00Z">…</article> <article data-activity data-time="2026-05-13T17:00:00Z">…</article></activity-feed>
Add data-activity-avatar="URL" for a circular avatar via <user-avatar>, or data-activity-icon="name" for a Lucide icon via <icon-wc>. Mutually exclusive — avatar wins if both are set.
<article data-activity data-time="..." data-activity-avatar="/users/ada.jpg"> <a href="/users/ada">Ada</a> commented on <a href="/issues/482">issue #482</a></article> <article data-activity data-time="..." data-activity-icon="git-merge"> <a href="/users/bob">Bob</a> merged <a href="/pull/120">PR #120</a></article>
Set data-infinite to add an IntersectionObserver sentinel at the end of the feed. When the sentinel enters the viewport, activity-feed:load-more fires; the author appends new entries (via addEntry or direct DOM insertion) and the sentinel re-arms automatically.
const feed = document.querySelector('activity-feed');let cursor = null; feed.addEventListener('activity-feed:load-more', async () => { const { entries, next } = await api.getActivity({ cursor }); for (const entry of entries) { feed.addEntry({ time: entry.timestamp, html: entry.bodyHtml, avatar: entry.avatarUrl, }); } cursor = next;});
When the feed has zero entries, the component renders data-empty-text (default No recent activity
). The empty state is auto-removed when the first entry is added.
<activity-feed data-empty-text="No activity yet — check back later."></activity-feed>
role="feed" per the WAI-ARIA Feed pattern.article role with aria-posinset / aria-setsize set automatically (so AT can announce "article 3 of 50").role="heading" at aria-level="3".<time datetime> with the absolute timestamp in title; relative text auto-refreshes every 60s.| Attribute | Type | Default | Description |
|---|---|---|---|
aria-label | string | Recent activity | Region label. |
data-group | string | none | none | day | week — date headings between entries. |
data-infinite | boolean | false | Adds an IntersectionObserver sentinel; emits activity-feed:load-more. |
data-empty-text | string | No recent activity | Empty-state text. |
<article data-activity>)| Attribute | Description |
|---|---|
data-time | ISO-8601 timestamp; required. |
data-activity-avatar | Avatar URL (renders <user-avatar>). |
data-activity-icon | icon-wc name (alternative to avatar). |
| Event | Bubbles | Detail |
|---|---|---|
activity-feed:load-more | yes | — |
| Method | Description |
|---|---|
addEntry(data, opts?) | Insert an entry. data: { time, html, avatar?, icon? }; opts: { prepend? }. |
removeEntry(entry) | Remove a single entry element. |
clear() | Remove every entry (empty-state will appear). |
entries | Live list of decorated entry elements. |
Authors who prefer can also append <article data-activity> directly to the host — the internal MutationObserver picks up the new entry and decorates it.
<comment-thread> — threaded, mutable discussion.<recently-visited> — browser navigation history.<time-index> — changelog / version timeline.