# Activity feed

> A card timeline of recent events — avatar, action text, and a relative timestamp per entry.

A shadcn/ui block from the [@quill](https://www.quilldesignsystem.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/quill/activity-feed).

- Page, with a live preview: https://shadcn.exchange/r/quill/activity-feed
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/quill/activity-feed-light-ebd966228a47fc6b.png
- Registry item: https://www.quilldesignsystem.com/r/activity-feed.json
- Categories: data-display
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://www.quilldesignsystem.com/r/activity-feed.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `card`, `avatar`, `badge`, `https://www.quilldesignsystem.com/r/icon.json`

## Notes from the registry

A card timeline of recent events — avatar, action text, and a relative timestamp per entry. Do: Use relative timestamps ("2 hours ago") for recent activity — they read faster than absolute dates. Don't: Force users to parse a full timestamp for something that happened minutes ago. Use notifications when the events need an unread/read state and a mark-all-read action, not just a read-only history. Use data-table when the events are really records you need to filter, sort, or act on, not just skim chronologically.

## Files

### `registry/blocks/activity-feed.tsx`

```tsx
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card'
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
import { Badge } from '@/components/ui/badge'
import { Icon } from '@/components/ui/icon'

const events = [
  {
    who: 'AL',
    name: 'Ada Lovelace',
    action: 'published',
    what: 'The Brief · 004',
    when: '2 hours ago',
    icon: 'ink_pen',
  },
  {
    who: 'JD',
    name: 'John Doe',
    action: 'commented on',
    what: 'Discovery findings',
    when: 'Yesterday',
    icon: 'chat_bubble',
  },
  {
    who: 'MM',
    name: 'Meeting Mapper',
    action: 'archived',
    what: 'Q2 planning notes',
    when: '2 days ago',
    icon: 'archive',
  },
  {
    who: 'AL',
    name: 'Ada Lovelace',
    action: 'invited',
    what: 'two new collaborators',
    when: 'Last week',
    icon: 'person_add',
  },
] as const

/** A card timeline of recent events — avatar, action text, and a relative timestamp per entry. */
export function ActivityFeed() {
  return (
    <Card className="w-[420px]">
      <CardHeader>
        <div className="flex items-center justify-between gap-2">
          <CardTitle>Activity</CardTitle>
          <Badge variant="ghost">This week</Badge>
        </div>
        <CardDescription>What happened while you were away.</CardDescription>
      </CardHeader>
      <CardContent>
        <ol className="relative flex flex-col gap-5 before:absolute before:top-2 before:bottom-2 before:left-[15px] before:w-px before:bg-border">
          {events.map((e, i) => (
            <li key={i} className="relative flex items-start gap-3">
              <Avatar className="relative z-1 ring-4 ring-card">
                <AvatarFallback>{e.who}</AvatarFallback>
              </Avatar>
              <div className="flex min-w-0 flex-1 flex-col gap-0.5 pt-1">
                <p className="text-sm leading-snug">
                  <span className="font-medium text-foreground">{e.name}</span>{' '}
                  <span className="text-muted-foreground">{e.action}</span>{' '}
                  <span className="font-medium text-foreground">{e.what}</span>
                </p>
                <span className="flex items-center gap-1 text-xs text-muted-foreground">
                  <Icon name={e.icon as never} size={12} aria-hidden /> {e.when}
                </span>
              </div>
            </li>
          ))}
        </ol>
      </CardContent>
    </Card>
  )
}
```
