Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Data Display

Timeline

Data Display

Timeline React component for Tailwind CSS v4

TagTree View
Live props
Appearance
variant
size
markers
tone
color
timeFormat
line
Behavior
order
expandable
animateIn
locale
Layout
groupByDay
stickyGroups
timePosition
State
loading
skeletonCount4
hasMore
loadingMore
Code
import { Timeline } from "@/components/ui/timeline"

<Timeline />
  1. Home
  2. Components
  3. Data Display
  4. Timeline
Pro

Timeline React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React timeline component for activity feeds, audit trails, changelogs and order tracking, with icons, relative times and day groups.

Read it top to bottom: each event has a marker on the line (a dot, an icon tile in its tone, or the person's avatar), a title, an optional description and a timestamp that stays fresh ("5 min. ago", "yesterday"); hover a timestamp on desktop for the full date and time (use timeFormat "datetime" where people on phones need it). Items with details show a Details button: click or tap it (Enter or Space from the keyboard) and the details slide open while the chevron turns; press again to close. New events slide in at the top and the rest glide down. In the feed variant events sit under day headings ("Today", "Yesterday") that can stick while you scroll. In the tracking variant finished steps get a check, the current step pulses, upcoming ones are hollow, a failed one turns red with an X, and the line fills down to the current step when it scrolls into view. Show older at the end loads more and spins while it waits. Tab only stops on real controls (links in titles, Details, Show older, buttons in meta). On a phone or in a narrow panel the side date column folds under the title and every button has a 44px touch target. Reduced motion drops the stagger, the growth and the pulse.

Timeline is a Pro React component in the React data display components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add timeline, together with Avatar, Button, Empty State, Skeleton, useControllableState and 5 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add timeline

Timeline is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import Timeline from @/components/ui/timeline.

import { Phone, Receipt } from "lucide-react"
import { Timeline, type TimelineItemData } from "@/components/ui/timeline"

const activity: TimelineItemData[] = [
  {
    id: "payment",
    title: "Invoice #2026-0412 paid",
    description: "$12,480.00 by bank transfer",
    time: "2026-09-24T16:20:00",
    icon: <Receipt />,
    tone: "success",
  },
  {
    id: "call",
    title: "Call with Adam Foster",
    description: "12 min",
    time: "2026-09-23T11:05:00",
    icon: <Phone />,
    actor: { name: "Michael Reed", avatar: "/demo/mihai-ionescu.jpg" },
    details: "Asked for training for 2 new teammates.",
  },
]

export function ClientActivity() {
  return (
    <Timeline
      items={activity}
      locale="en-US"
    />
  )
}

// parts, for events the items API does not cover (same file, same look):
// TimelineRoot takes the appearance props of Timeline (variant, size, tone, color, line, timePosition,
//   timeFormat, locale, labels, classNames) and holds the shared clock and locale;
// TimelineGroup { label, sticky } is a day heading and its <ol>; without a label it is just the list;
// TimelineItem { status, tone, color, expanded, index } is one <li>; status only applies in the tracking
//   variant, where the current step gets aria-current="step";
// TimelineMarker { kind: "dot" | "icon" | "avatar" | "status", icon, actor, children } is the marker and
//   the rail down to the next event (hidden on the last one);
// TimelineContent, TimelineTitle { href }, TimelineDescription: the text column, the title (a link with
//   href; tracking steps read their status first to screen readers) and the secondary text;
// TimelineTime { value, format, placement: "inline" | "side" } is a <time> with dateTime and a full-date
//   title once hydrated; side puts it in the start column on timelines 512px wide and up;
// TimelineDetails { open, defaultOpen, onOpenChange, collapsible } is the Details toggle and its panel;
// DEFAULT_TIMELINE_LABELS holds the English strings to spread a translation over
import { Truck } from "lucide-react"
import {
  TimelineRoot, TimelineGroup, TimelineItem, TimelineMarker, TimelineContent, TimelineTitle,
  TimelineDescription, TimelineTime, TimelineDetails,
} from "@/components/ui/timeline"

export function Delivery({ tracking, sentAt }: { tracking: string; sentAt: string }) {
  return (
    <TimelineRoot variant="tracking" locale="en-US" aria-label="Delivery">
      <TimelineGroup>
        <TimelineItem status="current" index={0}>
          <TimelineMarker kind="icon" icon={<Truck />} />
          <TimelineContent>
            <TimelineTitle href={`/parcels/${tracking}`}>Handed to the courier</TimelineTitle>
            <TimelineDescription>Tracking {tracking}</TimelineDescription>
            <TimelineTime value={sentAt} />
            <TimelineDetails>Estimated delivery tomorrow, between 10 AM and 2 PM.</TimelineDetails>
          </TimelineContent>
        </TimelineItem>
      </TimelineGroup>
    </TimelineRoot>
  )
}

Props

  • itemsdefault "[]"
    TimelineItemData[]

    The events: { id, title, description?, time?: Date | string | number, icon?, tone?, color?, status?: "complete" | "current" | "upcoming" | "error", actor?: { name, avatar? }, meta?, details?, href? }. Items without a time sort last and group under labels.earlier.

  • variantdefault "default"
    "default" | "feed" | "tracking"

    default: markers on a line with title, description and time. feed: compact activity rows with avatars and one sentence (bold the names with <b>), grouped by day, no line. tracking: order steps with status markers and a line filled up to the current step.

  • sizedefault "md"
    "sm" | "md" | "lg"

    Marker 20 / 28 / 36px (dots 8 / 10 / 12px), title 13 / 14 / 15px, and the spacing between events.

  • markersdefault "auto"
    "auto" | "dot" | "icon" | "avatar"

    auto: the icon when an item has one, else the actor's avatar, else a dot (feeds prefer the avatar). The others force a kind and fall back when an item lacks it. Tracking steps always show their status.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The default marker tone: dots, tracking checks and the filled line. Each item can set its own.

  • colordefault ""
    string

    Any CSS color for the markers; replaces the tone. Items with their own tone keep it.

  • orderdefault "desc"
    "desc" | "asc" | "none"

    Sorts by time: desc newest first (feeds, logs), asc oldest first (tracking, where it is the default), none keeps your order. Items without a time go last.

  • groupByDaydefault false
    boolean

    Day headings ("Today", "Yesterday", "Monday", then the date) in the locale. On by default in the feed variant.

  • stickyGroupsdefault false
    boolean

    Day headings stick to the top of the scroll container. Set --timeline-sticky-top to clear a sticky header and --timeline-bg to the surface color behind them.

  • timeFormatdefault "relative"
    "relative" | "time" | "date" | "datetime"

    relative: "5 min. ago", "yesterday", then the date after 4 weeks (under an older day heading it prints the clock time, since the heading names the day). time: 4:40 PM. date: 9/22/2026. datetime: both. Times refresh every minute.

  • timePositiondefault "inline"
    "inline" | "side"

    inline: the time at the end of the title row. side: a date column on the start side (changelogs, audit logs) once the timeline itself is 512px wide; narrower (a phone, a side panel) it folds under the title. It is a container query, so it follows the space the timeline gets, not the screen.

  • linedefault true
    boolean

    The connector between markers. Off by default in the feed variant.

  • expandabledefault true
    boolean

    Items with details get a Details toggle. Off shows every item's details open.

  • expandedKeys
    string[]

    Ids of the items whose details are open (controlled). Pair with onExpandedKeysChange.

  • defaultExpandedKeysdefault "[]"
    string[]

    Ids open at first while uncontrolled.

  • onExpandedKeysChange
    (keys: string[]) => void

    Fires with the open ids when a Details toggle is pressed.

  • loadingdefault false
    boolean

    Skeleton events (marker and two lines) in place of the items, with aria-busy.

  • skeletonCountdefault 4
    number

    How many skeleton events show while loading (1 to 12).

  • hasMoredefault false
    boolean

    Shows a Show older button at the end (needs onLoadMore).

  • onLoadMore
    () => (void | Promise<unknown>)

    Called by Show older. A returned promise keeps the button spinning until it settles.

  • loadingMoredefault false
    boolean

    Spins the Show older button when you track the request yourself (shown while hasMore is on).

  • emptyState
    ReactNode

    Replaces the default <EmptyState size="sm" preset="empty" title={labels.empty} /> when there are no items.

  • error
    ReactNode

    Shows the error empty state with this message in place of the items.

  • onRetry
    () => (void | Promise<unknown>)

    Adds Try again to the error state; a returned promise shows it loading.

  • animateIndefault true
    boolean

    Events rise in one after another when the list appears after the page loaded (after skeletons, on a tab switch), at most 12, and new events slide in at the top. Off, new events simply appear while the rest still glide down. Server-rendered lists show at once.

  • localedefault ""
    string

    Locale for times and day headings, e.g. en-US. Empty uses UIProvider's, then the browser's.

  • labels
    TimelineLabelsInput

    Strings: showDetails ("Details"), hideDetails ("Hide details"), loadMore ("Show older"), earlier ("Earlier"), empty ("No activity yet"), status { complete, current, upcoming, error } read before tracking titles. English defaults in DEFAULT_TIMELINE_LABELS.

  • classNames
    Partial<Record<"group" | "groupLabel" | "list" | "item" | "rail" | "marker" | "content" | "header" | "title" | "description" | "actor" | "meta" | "time" | "toggle" | "details" | "footer", string>>

    Extra classes for the inner parts.

  • className
    string

    Extra classes for the root. Set --timeline-bg here when sticky headings sit on a surface.

Parts

  • TimelineRoot

    component

    The outer part of a hand-built timeline. Takes the appearance props of Timeline (variant, size, tone, color, line, timePosition, timeFormat, locale, labels, classNames) and shares the clock and locale with every part inside.

  • TimelineGroup

    component

    A day heading and its list. Without a label it renders just the list.

    • label
      ReactNode

      The heading, such as Today, Yesterday or Sep 12, 2026.

    • stickydefault false
      boolean

      The heading sticks while scrolling (top from --timeline-sticky-top, fill from --timeline-bg).

  • TimelineItem

    component

    One event as a list item. status applies in the tracking variant, where the current step gets aria-current="step".

    • status
      "complete" | "current" | "upcoming" | "error"

      The step state in the tracking variant.

    • tone
      "neutral" | "brand" | "success" | "warning" | "danger" | "info"

      Marker tone for this event; overrides the timeline's.

    • color
      string

      Any CSS color for the marker; replaces the tone.

    • expanded
      boolean

      Sets data-expanded on the item, for styling it while its details are open.

    • index
      number

      Position in the list; delays the tracking line growth.

  • TimelineMarker

    component

    The marker and the rail down to the next event (hidden on the last one).

    • kind
      "dot" | "icon" | "avatar" | "status"

      What the marker shows.

    • icon
      ReactNode

      The icon for kind icon.

    • actor
      { name: string; avatar?: string }

      The person for kind avatar.

    • children
      ReactNode

      Replaces the marker's content.

  • TimelineContent

    component

    The text column next to the marker: title, description, meta and details.

  • TimelineTitle

    component

    The event title. With href it becomes a link; tracking steps read their status first to screen readers.

  • TimelineDescription

    component

    The secondary text under the title, in the muted color.

  • TimelineTime

    component

    A time element with a machine-readable dateTime and a full-date title once hydrated, formatted relative or as a clock time.

    • value*
      Date | string | number

      The moment of the event.

    • format
      TimestampFormat

      Overrides the timeline's timeFormat.

    • placementdefault "inline"
      "inline" | "side"

      side puts it in the start column on timelines 512px wide and up.

  • TimelineDetails

    component

    The Details toggle and its collapsible panel under an event.

    • open
      boolean

      Controlled open state.

    • defaultOpendefault false
      boolean

      Starting state when uncontrolled.

    • onOpenChange
      (open: boolean) => void

      Fires when the toggle opens or closes the panel.

    • collapsibledefault true
      boolean

      false shows the content without a toggle.

  • DEFAULT_TIMELINE_LABELS

    constant

    The English strings (showDetails, hideDetails, loadMore, earlier, empty and the tracking status words). Spread it to translate. Type: TimelineLabels.

  • TimelineItemData

    type

    One event of the items prop: id, title, description, time, icon, tone or color, status, actor, meta, details and href.

Examples

Activity feed

variant="feed": avatars, bold names, day headings and a Show older button that loads.

Order tracking

variant="tracking": checks, a pulsing current step and a line that fills as it scrolls in.

Changelog

timePosition="side" puts dates in a column when there is room (a container query); details hold the release notes.

Audit log

size="sm", full date and time, a tone per action type.

Failed step

An error status turns the step red; meta carries the recovery action.

Loading and empty

Skeletons in the real row shape, then the shared empty state.

Dependencies

motionlucide-reactavatarbuttonempty-stateskeletonuse-controllable-stateuse-localemotionrelative-timetonesui-config

Files

  • components/ui/timeline.tsx

FAQ

Is the React Timeline component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Timeline?

Run npx wingo-ui@latest add timeline. It also adds the Wingo UI items it builds on (Avatar, Button, Empty State, Skeleton, useControllableState and 5 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/timeline.

How does Timeline behave on phones?

Read it top to bottom: each event has a marker on the line (a dot, an icon tile in its tone, or the person's avatar), a title, an optional description and a timestamp that stays fresh ("5 min. ago", "yesterday"); hover a timestamp on desktop for the full date and time (use timeFormat "datetime" where people on phones need it).

See also

StepsNotification CenterAvatarRelative timelibEmpty State

Used in

Order trackerApproval FlowAdmin Dashboard PageCRM Contact PageCRM Pipeline PageKPI DashboardProfile PageStatus PageTravel Page

More React data display components

Tree ViewKbdBadgeFile managerCountdownCalendar View
All 23 React data display components

Articles using this component

  • Audit Log UI in React: Filters, Diffs and CSV Export

    Component guides · Nov 6, 2026 · 10 min read

  • Changelog Page Template in React: RSS, Email, Permalinks

    Build it: screens and apps · Nov 6, 2026 · 10 min read

  • React Gantt Chart: Drag, Progress and Dependencies

    Component guides · Oct 19, 2026 · 10 min read

PreviousTagNext Tree View

Client activity

Tech Solutions Inc. · New York

  1. Invoice #2026-0412 paid
    09/24/2026, 4:22 PM
    $12,480.00 by bank transfer
    Olivia Bennett
    Paid
  2. Offer #OF-2026-118 accepted
    09/24/2026, 1:40 PM
    Adam Foster signed the offer online.
    Emma Carter
    Northwind CRM Pro license, 12 months$8,400.00
    Setup and data import$3,200.00
    Team training (2 sessions)$880.00
    Total$12,480.00
  3. Call with Adam Foster
    09/23/2026, 2:40 PM
    12 min · asked for training for 2 new teammates
    Michael Reed
  4. Email sent: “Order confirmation”
    09/21/2026, 4:40 PM
    Opened 3 times
    Emma Carter
  5. Client created
    09/12/2026, 4:40 PM
    Tech Solutions Inc. · New York · EIN 12-3456789
    Michael Reed

Add an event to see it slide in at the top