Feed
Present live product activity while loading older or newer events without losing reading position.
Usage
Finite feed
Review the complete activity history for one release.
<!-- Feed / Finite -->
<script setup lang="ts">
import { FeedItem, FeedRoot } from '@sectile/vue/feed'
const activities = [
{ id: 'deployed', title: 'Production deployment completed', summary: 'Release 2026.08 is healthy in all regions.', actor: 'Deploy bot', occurredAt: 'Just now' },
{ id: 'approved', title: 'Release approved', summary: 'Mina approved the production promotion.', actor: 'Mina Kim', occurredAt: '18 min ago' },
{ id: 'checks', title: 'Required checks passed', summary: 'All 12 release checks completed.', actor: 'CI', occurredAt: '24 min ago' },
] as const
const itemIDs = activities.map(({ id }) => id)
const getPosition = (id: string) => itemIDs.indexOf(id) + 1
</script>
<template>
<FeedRoot :items="itemIDs" :set-size="activities.length" :get-position="getPosition" label="Release activity">
<FeedItem v-for="event in activities" :key="event.id" :value="event.id" as="article">
<strong>{{ event.title }}</strong><p>{{ event.summary }}</p>
<small>{{ event.actor }} · {{ event.occurredAt }}</small>
</FeedItem>
</FeedRoot>
</template>Examples
Load newer items
Surface new deployment updates without interrupting the activity currently being read.
<!-- Feed / Load After -->
<script setup lang="ts">
import { computed, ref } from 'vue'
import { FeedItem, FeedLoadNewer, FeedRoot } from '@sectile/vue/feed'
interface Activity { id: string; title: string; summary: string; actor: string; occurredAt: string }
interface ActivityWindow { items: Activity[]; revision: number }
const initialActivity: Activity[] = [
{ id: 'approved', title: 'Release approved', summary: 'Mina approved the production promotion.', actor: 'Mina Kim', occurredAt: '18 min ago' },
{ id: 'checks', title: 'Required checks passed', summary: 'All 12 release checks completed.', actor: 'CI', occurredAt: '24 min ago' },
]
const activities = ref<Activity[]>(initialActivity)
const revision = ref(20)
const availableUpdates = ref(2)
const itemIDs = computed(() => activities.value.map(({ id }) => id))
async function loadNewer(direction: 'before' | 'after', anchor: string | null, currentRevision: number) {
if (direction !== 'after') return
const query = new URLSearchParams({ after: anchor ?? '', revision: String(currentRevision) })
const response = await fetch(`/api/releases/2026.08/activity?${query}`)
if (!response.ok) throw new Error('Could not load recent activity')
const window = await response.json() as ActivityWindow
activities.value = [...window.items, ...activities.value]
revision.value = window.revision
availableUpdates.value = 0
}
</script>
<template>
<FeedRoot :items="itemIDs" :revision="revision" label="Live release activity" @request-window="loadNewer">
<FeedLoadNewer v-if="availableUpdates > 0" v-slot="{ pending }">
{{ pending === 'after' ? 'Checking for updates…' : `${availableUpdates} new updates` }}
</FeedLoadNewer>
<FeedItem v-for="event in activities" :key="event.id" :value="event.id" as="article">
<strong>{{ event.title }}</strong><p>{{ event.summary }}</p>
<small>{{ event.actor }} · {{ event.occurredAt }}</small>
</FeedItem>
</FeedRoot>
</template>Load earlier items
Append older release events while preserving the current activity order.
<!-- Feed / Load Before -->
<script setup lang="ts">
import { computed, ref } from 'vue'
import { FeedItem, FeedLoadEarlier, FeedRoot } from '@sectile/vue/feed'
interface Activity { id: string; title: string; summary: string; actor: string; occurredAt: string }
interface ActivityWindow { items: Activity[]; revision: number; hasEarlier: boolean; total: number }
const initialActivity: Activity[] = [
{ id: 'deployed', title: 'Production deployment completed', summary: 'Release 2026.08 is healthy in all regions.', actor: 'Deploy bot', occurredAt: 'Just now' },
{ id: 'approved', title: 'Release approved', summary: 'Mina approved the production promotion.', actor: 'Mina Kim', occurredAt: '18 min ago' },
]
const activities = ref<Activity[]>(initialActivity)
const revision = ref(12)
const hasEarlier = ref(true)
const total = ref(48)
const itemIDs = computed(() => activities.value.map(({ id }) => id))
async function loadEarlier(direction: 'before' | 'after', anchor: string | null, currentRevision: number) {
if (direction !== 'before' || !hasEarlier.value) return
const query = new URLSearchParams({ before: anchor ?? '', revision: String(currentRevision) })
const response = await fetch(`/api/releases/2026.08/activity?${query}`)
if (!response.ok) throw new Error('Could not load release history')
const window = await response.json() as ActivityWindow
activities.value = [...activities.value, ...window.items]
revision.value = window.revision
hasEarlier.value = window.hasEarlier
total.value = window.total
}
</script>
<template>
<FeedRoot :items="itemIDs" :revision="revision" :set-size="total" label="Release activity" @request-window="loadEarlier">
<FeedItem v-for="event in activities" :key="event.id" :value="event.id" as="article">
<strong>{{ event.title }}</strong><p>{{ event.summary }}</p>
<small>{{ event.actor }} · {{ event.occurredAt }}</small>
</FeedItem>
<FeedLoadEarlier v-if="hasEarlier" v-slot="{ pending }">
{{ pending === 'before' ? 'Loading history…' : 'Load earlier events' }}
</FeedLoadEarlier>
</FeedRoot>
</template>API
Vue package: @sectile/vue/feed
FeedRootFeedItemFeedLoadEarlierFeedLoadNewer
Props
FeedRootProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
defaultHighlightedValueInitially highlighted value for uncontrolled state.
disabledWhether interaction is unavailable.
getPositionReturns the numeric position represented by an item.
itemsOrdered item values managed by the component.
labelAccessible name announced for the control.
requestGenerationGeneration token returned by the window request being resolved.
revisionApplication revision used to refresh derived content.
setSizeTotal number of items represented by the current feed window.
FeedPartProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
Slots
FeedRootSlotProps
disabledWhether interaction is unavailable.
highlightedValueValue currently highlighted for interaction.
pendingWhether a request is currently pending.
requestGenerationGeneration of the current or most recently issued window request.
revisionRevision of the current state snapshot.
FeedItemSlotProps
disabledWhether interaction is unavailable.
highlightedWhether this item is highlighted for interaction.
highlightedValueValue currently highlighted for interaction.
pendingWhether a request is currently pending.
requestGenerationGeneration of the current or most recently issued window request.
revisionRevision of the current state snapshot.
valueCurrent value exposed by this contract.
Events
FeedRoot
highlightEmitted when the highlighted item changes.
requestWindowEmitted when the feed needs items outside the current window.
Other types
FeedPositionResolver
type FeedPositionResolver = NonNullable<FeedRootProps['getPosition']>FeedHighlightHandler
type FeedHighlightHandler = (value: string | null) => voidFeedRequestWindowHandler
type FeedRequestWindowHandler = (direction: FeedDirection, anchor: string | null, revision: number, requestGeneration: number) => voidFeedDirection
type FeedDirection = CollectionWindowDirectionParts
Shared scope: [data-scope="feed"]. Combine it with a part selector to keep styles local to this component.
| Part | Selector | Role | Extra attributes |
|---|---|---|---|
root | [data-part="root"] | Defines the component boundary and owns its composed parts. | — |
item | [data-part="item"] | Represents one selectable or actionable item. | — |
load-earlier | [data-part="load-earlier"] | Requests items before the visible feed window. | — |
load-newer | [data-part="load-newer"] | Requests items after the visible feed window. | — |
Keyboard interaction
| Key | Behavior |
|---|---|
| Arrow Down / Page Down | Move to the next article. |
| Arrow Up / Page Up | Move to the previous article. |
| Tab | Move into interactive controls inside the current article. |
Accessibility
The root uses feed semantics and each item is an article with optional position and set-size metadata.
See the corresponding WAI-ARIA pattern for the host accessibility contract.
