Activity Feed
ProA vertical timeline of what happened: comments, mentions, status changes, uploads, members joining. One rail threads every event, while each row varies its marker and its body, from a terse audit log to a rich inbox.
A workflow at a glance#
An episode moves from interview review to release in a series of small handoffs. Activity Feed keeps the full sequence readable; a compact phase bar makes the total production time easy to scan.
Installation#
Usage#
Activity Feed is composed from named parts, like Ranking. The ActivityFeed root is a semantic <ol> that owns density and the connecting rail; each ActivityItem pairs an ActivityMarker (drop an ActivityIcon, an Avatar, or an ActivityDot inside it) with an ActivityContent body. The rail is drawn automatically and closes itself on the last event.
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Anatomy#
import {
ActivityFeed,
ActivityGroup,
ActivityGroupLabel,
ActivityItem,
ActivityMarker,
ActivityIcon,
ActivityContent,
ActivityHeader,
ActivityActor,
ActivityTime,
ActivityBody,
ActivityCard,
ActivityAttachments,
ActivityImage,
ActivityActions,
} from "@/components/ui/activity-feed"
<ActivityFeed>
<ActivityGroup>
<ActivityGroupLabel />
<ActivityItem>
<ActivityMarker>
<ActivityIcon />
</ActivityMarker>
<ActivityContent>
<ActivityHeader>
<ActivityActor />
<ActivityTime />
</ActivityHeader>
<ActivityBody />
<ActivityCard />
<ActivityAttachments>
<ActivityImage />
</ActivityAttachments>
</ActivityContent>
<ActivityActions />
</ActivityItem>
</ActivityGroup>
</ActivityFeed>Playground#
Flip the knobs and watch the snippet keep up. Note that the two groups do different things: Props are values the component takes, while Parts are children you compose in or leave out, which is why turning one off removes an element from the code rather than adding a show-something prop. Turn them all off and the event still holds its alignment.
- Marcus Leementioned you in Q3 roadmap
- Priya Nairuploaded the press kit
- Sarah Chenclosed the milestone
Cross-cutting spacing axis.
What separates one event from the next.
Applied to the first event here.
Hover, press and focus affordance.
An icon, an Avatar, or a dot.
The relative timestamp.
A muted one-liner under the header.
Trailing controls for the row.
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Only the parts you need#
There is no showTime or showBody prop, because there is nothing to switch off: an event is whatever parts you put in it. Every part is droppable, and whichever one leads the content takes over the alignment, so a header-only event, a body-only event, and a card-only event all sit on the same axis as their marker.
Markers#
The marker carries the type of event. ActivityIcon is a soft tinted tile: pass a Koala glyph and a tone (brand, success, info, destructive, purple, and more). For people, drop an Avatar in the same slot; for minor events, ActivityDot is a low-emphasis dot. All three share a 32px footprint, so the rail stays aligned whichever you use.
Attachments#
Events carry payloads, and the feed composes them from existing components rather than re-rolling chips. Stack them in ActivityAttachments: drop a File Card row in for each file (it already ships the type-tinted icon, name, and meta) and lay ActivityImage thumbnails out in a flex flex-wrap row for an image gallery. For an upload-in-progress state, use File Card’s FileCardProgress; to collect the files in the first place, see File Upload.
Quoted comments#
ActivityCard is a nested surface for quoted content: a posted comment, a review, a changelog note. Its radius steps down concentrically from the Card the feed usually sits in. For a short, muted one-liner under the header, use ActivityBody instead.
Unread#
Mark an event as new to this reader with unread on ActivityItem: the header comes up to full-strength text and an ink dot rides the end of the row (with a screen-reader label). Deliberately no tinted row fill, since a feed is chrome-less and a band would read as a container.
Interactive events#
Pass asChild with an <a> child to make a whole event a link: the <li> stays the list item while the child becomes the hover-lit surface, so the markup is valid <li><a>. Use interactive on its own for a row that takes an onClick. The press feedback deepens the fill rather than scaling the row, the same call List makes for flush rows.
Row actions#
ActivityActions is a trailing slot for per-event controls: a reply, a download, an overflow menu. It sits beside ActivityContent rather than inside the header (which wraps), and parks on the marker’s axis so it rides the first line however tall the event grows. Don’t combine it with asChild, which would nest a control inside a link.
Day groups#
ActivityGroup gathers a run of events under a heading, a day or any other bucket, with an ActivityGroupLabel first. The label sits in the events' text column and the rail runs straight past it, so the timeline stays one line from the first event to the last. Each group is its own list, named by its label for a screen reader (“Today, list, 2 items”). The label is droppable, and a divided feed or one without the rail groups the same way.
Divided#
divided swaps the vertical rail for a hairline rule between events: the audit-log and notification-inbox reading. It re-balances the per-event rhythm so each rule sits evenly between its neighbours, and it takes the rail with it. A rule and a rail are two answers to the same question, so a divided feed never draws the connector and connector is ignored there.
Without the rail#
Set connector={false} on the feed for a plain list of events with no connecting line, useful for a single notification, a digest, or a card where the timeline framing would be too heavy.
Density#
Density is Koala’s cross-cutting spacing axis (see Density). For the feed it tunes the gap to the rail and each event’s own padding; the marker size stays fixed so the rail aligns identically at both densities. compact is the default; comfortable opens it up for a roomier inbox. Set it per-feed or for a whole subtree with DensityProvider.
API reference#
ActivityFeed
The root, a semantic <ol> that owns density and the connecting rail. Forwards all native <ol> props. Every part accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| density | "comfortable" | "compact" | "compact" | Per-event rhythm and the gap to the rail. Falls back to the nearest DensityProvider, then compact. |
| divided | boolean | false | A hairline rule between events instead of the rail (the audit-log reading). |
| connector | boolean | true | Draws the vertical rail between markers. Ignored when divided is set. |
ActivityGroup
A run of events under one heading, such as a day. Renders an <li> of the feed holding its own <ol>, named by its label. Forwards all native <li> props.
ActivityGroupLabel
The small heading of a group ("Today"), first in the group and droppable. Renders an <li> that becomes the accessible name of the group's list. Forwards all native <li> props.
ActivityItem
One event, an <li>. Forwards all native <li> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Renders the row as its single child (an <a> or <button>) inside the <li>. Implies interactive; do not pair with ActivityActions. |
| interactive | boolean | false | Adds the hover fill, press and focus ring. Defaults to true when asChild is set. |
| unread | boolean | false | Marks the event as new: full-strength header text plus an ink dot (and an sr-only "Unread"). |
ActivityMarker
The rail column. Holds an ActivityIcon, ActivityDot or Avatar and appends the rail itself, closed on the last event. Forwards all native <div> props.
ActivityIcon
A 28px tinted tile marker. Pass a Koala glyph as children. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| tone | "default" | "brand" | "success" | "warning" | "info" | "destructive" | "purple" | "pink" | "teal" | "orange" | "default" | The soft tint, matched to the kind of event. |
ActivityDot
A low-emphasis dot marker for minor events, in the same footprint as ActivityIcon. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| tone | "default" | "brand" | "success" | "warning" | "info" | "destructive" | "purple" | "pink" | "teal" | "orange" | "default" | The solid color of the dot. |
ActivityContent
The text column of an event. Forwards all native <div> props.
ActivityHeader
The wrapping first line: actor, verb, target, time. Forwards all native <div> props.
ActivityActor
The person or thing that acted, a <span>. Forwards all native <span> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Renders the actor as its child element, such as a profile link. |
ActivityTime
The muted, tabular timestamp, a real <time>. Pass an ISO dateTime and a relative label as children. Forwards all native <time> props.
ActivityBody
Secondary text under the header. Forwards all native <div> props.
ActivityCard
A nested surface for quoted content (a comment, a review), on the concentric radius. Forwards all native <div> props.
ActivityAttachments
A vertical stack for the payload: FileCard rows and ActivityImage rows. Forwards all native <div> props.
ActivityImage
A 56px image thumbnail with the image-outline ring. className sizes the clipping wrapper; every other prop goes to the native <img>.
| Prop | Type | Default | Description |
|---|---|---|---|
| alt* | string | - | The image's alternative text. |
ActivityActions
Trailing controls for one event, a sibling of ActivityContent that rides the first line. Forwards all native <div> props.