# Activity Feed

Show recent events by people with avatars, times and attachments.

Project activity with an attachment and labels

**Pro** — This example installs with a Siteplane UI Pro license key. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/activity-feed-demo
```

This Pattern is part of [Siteplane UI Pro](/pricing), and its source comes with a [Pro license](/docs/license/#patterns). Its previews are free to view and try.

## Installation

**Pro** — Installs with a Siteplane UI Pro license key. Complete the one-time [Pro setup](/docs/installation/#siteplane-ui-pro) first. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/activity-feed
```

With npm: `npx shadcn@4.18.0 add @siteplane-pro/activity-feed`.

## Usage

```tsx
import {
  ActivityFeed,
  ActivityFeedItem,
} from "@/siteplane/components/activity-feed";

<ActivityFeed aria-label="Project activity">
  <ActivityFeedItem
    actor={{ name: "Mara Ellis" }}
    description="Added a file to Website redesign"
    timestamp="Just now"
  />
</ActivityFeed>;
```

## Composition

```text
ActivityFeed
└── ActivityFeedItem
    ├── ActivityFeedLink (in the description)
    ├── ActivityFeedAttachment
    ├── ActivityFeedMessage
    └── your own content, such as labels
```

The feed separates its items with a divider by default. Each item shows the portrait, the name and the time, then the description. An `actor.href` links the name and, for pointer users, the portrait to the same destination; the portrait stays out of the tab order, so keyboard and screen-reader users meet one link per person. Without `avatarUrl` the portrait shows initials taken from the name.

`status` adds the presence dot of [Avatar](/docs/components/avatar/#status-indicator) to the portrait, as `online`, `away`, `busy` or `offline`. The dot is named for assistive technology by `statusLabel`, or else by the English status name. `unreadLabel` adds an unread marker with that text as its accessible name. Anything passed as children appears below the description, such as attachments, a message or your own content.

## Connected layout

Set `variant="connected"` to draw a line through the portraits. It suits events that follow one another, such as the history of a task.

Connected activity with a message

**Pro** — This example installs with a Siteplane UI Pro license key. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/activity-feed-connected-example
```

## Spaced layout

Set `variant="spaced"` to separate the items with space only, for a lighter feed inside a card or a sheet.

Spaced team activity

**Pro** — This example installs with a Siteplane UI Pro license key. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/activity-feed-spaced-example
```

## Messages

`ActivityFeedMessage` sets what a person wrote in a bordered message surface below the name. Use the description for a short line such as a handle. Messages work in all three layouts, and an attachment can follow a message.

Messages with a video attachment

**Pro** — This example installs with a Siteplane UI Pro license key. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/activity-feed-messages-example
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| ActivityFeed.variant | ActivityFeedVariant: divided \| connected \| spaced | divided | Layout of the items: a divider between them, a line through the portraits, or space only. |
| ActivityFeed.aria-label | string | Activity feed | Accessible name of the list. Replace it with a name that fits the feed. |
| ActivityFeedItem.actor | { name, href?, avatarUrl?, initials? } | required | The person. name is shown before the time, href links the name and the portrait, avatarUrl sets the portrait image and initials replace the ones taken from the name. |
| ActivityFeedItem.timestamp | ReactNode | required | Visible time of the event. |
| ActivityFeedItem.dateTime | string | unset | Machine-readable time. With it the timestamp is a time element; without it, a plain span. |
| ActivityFeedItem.description | ReactNode | unset | What happened, such as a sentence with an ActivityFeedLink. |
| ActivityFeedItem.status | online \| away \| busy \| offline | unset | Avatar's presence dot on the portrait. |
| ActivityFeedItem.statusLabel | string | English status name | Accessible name of the dot. |
| ActivityFeedItem.presence | { state: online \| offline; label: string } | unset | Deprecated: the earlier form of status and statusLabel, which it maps to. Use status and statusLabel. |
| ActivityFeedItem.unreadLabel | string | unset | Shows an unread marker. The text is its accessible name. |
| ActivityFeedItem.children | ReactNode | unset | Content below the description, such as attachments, labels or a message. |
| ActivityFeedAttachment.fileName | string | required | File name, also used to choose the file type icon. |
| ActivityFeedAttachment.fileType | string | from fileName | Extension for the icon when it differs from the file name, for example for files without a suffix. |
| ActivityFeedAttachment.metadata | ReactNode | unset | Secondary line, such as the file size. |
ActivityFeedProps extends native ol props, ActivityFeedItemProps native li props
except the animation and drag handlers, which belong to the motion,
ActivityFeedLink native anchor props, ActivityFeedMessage native div props and
ActivityFeedAttachmentProps native figure props. All accept refs and string
className, and all but ActivityFeedAttachment take children: the attachment
renders its file icon, name and metadata itself. ActivityFeedLink renders an
inline link button and ActivityFeedMessage sets its children in a Card with the
message variant.

## Usage notes

The feed is an ordered list of articles. Pass the items newest first and give the list an `aria-label` that names it. Pass `dateTime` so the timestamp becomes a `time` element with a machine-readable value; without it, the timestamp is plain text.

The feed does not sort, group by day, paginate, virtualize or announce new items. For a long or live feed, add these in the application and announce new entries with your own live region.

Entries that are added or removed animate on their own. A new entry expands and fades in, and a removed one fades out and collapses; the divider or the connecting line opens and closes with it while the other entries glide. Give every `ActivityFeedItem` a stable `key`, because an index as the key makes a removed entry leave as its neighbor, and pass the entries as direct children of the feed, not inside a fragment. A leaving entry is inert, so move focus before you remove one that holds it. With reduced motion, from the system setting or `SiteplaneMotionProvider preset="reduced"`, every change is instant.

Status dots and unread markers are indicators only. An attachment shows the file and does not link or download it, so add a link or a button beside it when people need to open the file.
