Skip to content

Activity timeline

A record's tasks, events, calls and emails in time order, with their details. The Activity tab of a record page.

Installation

pnpm dlx shadcn@latest add @wisty/activity-timeline

The CLI installs the Radix or Base UI file to match your project, with what it builds on: button, checkbox, collapsible, dropdown-menu, expandable-section, icon, skeleton.

If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.

When to use

Use it

  • On a record page's Activity tab: the tasks, events, logged calls and emails on a record, like an account, a contact, an opportunity or a case, in time order. It's Lightning's Activity Timeline.
  • When people need what's next and overdue at the top, the past by month under it, and each activity's details one click away.
  • When people close tasks from the list with a checkbox, or act on an activity from its menu.

Don't use it

  • For posts, comments and updates. That's a Chatter feed, not activity.
  • For a list of records that aren't tasks, events, calls or emails. Use Related list, or compose Item.
  • For a record's stage or status. Use Path.
  • For one task or event on its own page. Use Record detail.

Usage

import { ActivityTimeline } from "@/components/wisty/activity-timeline"
<ActivityTimeline
  activities={activities}
  today="2026-06-15"
  timeZone="America/Chicago"
  onToggleComplete={(id, completed) => saveTask(id, { completed })}
  onAction={(action, activity) => runAction(action.value, activity.id)}
/>

Each activity is a plain record. Read them from the record's open activities and activity history, or from Task and Event:

  • type: "task" for a Task, "call" and "email" for a Task whose TaskSubtype is Call or Email, "event" for an Event.
  • when: a task's ActivityDate as a day ("2026-06-19"), or null when it has none. For an event, its StartDateTime; for a call or email, when it happened, both as instants ("2026-06-17T15:00:00Z").
  • completed: the task's IsClosed. who is the Name field (WhoId), related is Related To (WhatId), assignee is Assigned To (OwnerId), and description is the Comments, Description or email body.
  • details: more fields for the expanded row, in order: Call Result for a call, Location for an event, From Address and To Address for an email.

Open tasks, and events from today on, show under Upcoming & Overdue, soonest first. Completed tasks, earlier events, calls and emails show under their month, newest first. Pass expanded and onExpandedChange to keep the open rows yourself; defaultExpanded opens some at first.

Examples

No activity

An empty `activities` array: Upcoming & Overdue and the past each say what would show up there.

No next steps. To get things moving, add a task or set up a meeting.

No past activity. Past meetings and tasks marked as done show up here.

Unavailable

A timeline that couldn't load, from `activities={null}`, says so instead of showing no activity.

Activities aren't available.

Loading

With `loading` and no activities yet, placeholder rows hold the timeline's shape and it's marked busy.

API reference

ActivityTimeline

PropTypeDefaultDescription
activities*Activity[] | nullThe record's activities, in any order. null when they couldn't load.
todaystringToday, a day: "2026-06-15". Splits upcoming from past and marks overdue tasks. Default today in timeZone.
expandedstring[]The ids of the rows showing their details. Controlled.
defaultExpandedstring[][]The ids of the rows showing their details at first.
onExpandedChange(expanded: string[]) => void
onToggleComplete(activityId: string, completed: boolean) => voidCalled with the new state when a task's checkbox changes. Without it, the checkbox is read-only.
onAction(action: ActivityAction, activity: Activity) => voidCalled with the action picked from an activity's menu, and the activity.
loadingbooleanfalseDraws placeholder rows until the activities load, or marks the timeline busy over the ones it has.
localestring"en-US"A BCP 47 locale for dates. Default "en-US".
timeZonestring"UTC"An IANA time zone for the instants of events, calls and emails. Default "UTC".

Also takes every <div> (opens in a new tab) prop.

ActivityTypetype

type ActivityType = "task" | "event" | "call" | "email"

A record an activity points at, like a contact or an account.

MemberTypeDefault
name*string
hrefstring

ActivityFieldtype

A field in an activity's details, like Call Result.

MemberTypeDefault
label*string
value*string

ActivityActiontype

An item in an activity's actions menu.

MemberTypeDefaultDescription
value*stringIdentifies the action in onAction: "edit", "delete".
label*stringThe item's text.
disabledbooleanShows the item but doesn't let it run.

TaskActivitytype

A task: its date is a due day, and it can be completed.

MemberTypeDefaultDescription
type*"task"
when*string | nullThe due date (ActivityDate), a day: "2026-06-18". null when it has none.
completedbooleanThe task is closed (IsClosed).
id*stringThe Task or Event Id.
subject*stringThe Subject.
hrefstringThe activity's page. Links the subject.
whoActivityLinkThe Name field (WhoId): the contact or lead.
relatedActivityLinkThe Related To field (WhatId): the account, opportunity or case.
assigneeActivityLinkThe Assigned To field (OwnerId): the user.
descriptionstringThe Comments of a task or call, the Description of an event, the body of an email.
detailsActivityField[]More fields for the details, in order: Call Result, Location, To Address.
actionsActivityAction[]The row's menu.

TimedActivitytype

An event, logged call or email: its date is an instant.

MemberTypeDefaultDescription
type*"event" | "call" | "email"
when*stringWhen it starts or happened, an ISO instant: "2026-06-17T15:00:00Z".
id*stringThe Task or Event Id.
subject*stringThe Subject.
hrefstringThe activity's page. Links the subject.
whoActivityLinkThe Name field (WhoId): the contact or lead.
relatedActivityLinkThe Related To field (WhatId): the account, opportunity or case.
assigneeActivityLinkThe Assigned To field (OwnerId): the user.
descriptionstringThe Comments of a task or call, the Description of an event, the body of an email.
detailsActivityField[]More fields for the details, in order: Call Result, Location, To Address.
actionsActivityAction[]The row's menu.

Activitytype

type Activity = TaskActivity | TimedActivity

Accessibility

  • Each part is a level 2 heading over a list. Each activity is a list item whose subject is a level 3 heading, after the type's name ("Task", "Call").
  • Each row's toggle is "Toggle details for" its subject, with aria-expanded. The menu is "Show actions for" its subject.
  • A task's checkbox is "Mark … complete" with its subject.
  • An overdue date is red, and screen readers hear "Overdue:" before it.
  • While loading, the timeline is busy and its placeholders are hidden from screen readers.
  • The names in the line under a subject are underlined, so they don't rely on color alone. In Lightning Blue, the labels and links in an open row's details, and the date on a hovered row, are 4.15:1 to 4.17:1, below WCAG AA's 4.5:1, as in SLDS's own timeline.

Responsive behavior

  • The timeline takes its container's width. The subject truncates at three quarters of the row, with its full text on hover, so the date and menu always fit.
  • The details lay their fields out two to a row and truncate each value; the description wraps.

Rules

  • Pass onToggleComplete to let people close tasks. Without it, every task's checkbox is disabled: it shows whether the task is done, but nothing can change it.
  • Pass null to activities when they couldn't load, never []: an empty array says there's no activity.
  • Pass a task's due date as a day, never as midnight in some time zone. An instant would move to the day before for people west of UTC.
  • Pass today in the same time zone as timeZone. Overdue and the split between upcoming and past come from it.
  • Keep type to what the record is. A call logged as a Task with TaskSubtype Call is "call", not "task", so it has no checkbox.
  • Never pass asChild or render.