Timeline

free

Timeline arranges activity, history, and milestone entries in chronological order.

Preview
Dark

Monday, 28 February

  • Camila Simmons
    Camila Simmonsmentioned you in a commentPost08:34 PM
    @Carolyn There are two ways to write error-free programs; only the third one works.
  • Angelina Gotelli
    Angelina Gotelliaddedtranslation.xlsto ticketPD-109208:09 PM
  • Angelina Gotelli
    Angelina Gotellimentioned you in a commentPost08:06 PM
    @Carolyn Don't worry if it doesn't work right. If everything did, you'd be out of a job.
import Timeline from '@/components/ui/Timeline';
import Avatar from '@/components/ui/Avatar';
import Tag from '@/components/ui/Tag';
import { PiPaperclip } from 'react-icons/pi'

const EVENTS = [
  {
    id: 'first-comment',
    avatar: '/img/avatars/thumb-2.jpg',
    name: 'Camila Simmons',
    action: 'mentioned you in a comment',
    object: 'Post',
    time: '08:34 PM',
    mention: '@Carolyn',
    comment:
      'There are two ways to write error-free programs; only the third one works.',
  },
  {
    id: 'file-added',
    avatar: '/img/avatars/thumb-3.jpg',
    name: 'Angelina Gotelli',
    action: 'added',
    object: 'translation.xls',
    ticket: 'PD-1092',
    time: '08:09 PM',
  },
  {
    id: 'second-comment',
    avatar: '/img/avatars/thumb-3.jpg',
    name: 'Angelina Gotelli',
    action: 'mentioned you in a comment',
    object: 'Post',
    time: '08:06 PM',
    mention: '@Carolyn',
    comment:
      "Don't worry if it doesn't work right. If everything did, you'd be out of a job.",
  },
] as const;

export default function UsageDemo() {
  return (
    <div className="w-full max-w-2xl">
      <p className="mb-4 text-sm font-semibold uppercase text-muted-foreground">
        Monday, 28 February
      </p>
      <Timeline>
        {EVENTS.map((event) => (
          <Timeline.Item
            key={event.id}
            media={
              <Avatar
                size={24}
                src={event.avatar}
                alt={event.name}
              />
            }
          >
            <div className="grid gap-4">
              <div className="flex flex-wrap items-center gap-2 text-sm">
                <span className="font-medium">
                  {event.name}
                </span>
                <span className="text-muted-foreground">
                  {event.action}
                </span>
                {event.id === 'file-added' ? (
                  <span className="inline-flex items-center gap-2 text-foreground">
                    <PiPaperclip className="size-3.5" />
                    {event.object}
                  </span>
                ) : (
                  <span className="font-semibold text-foreground">
                    {event.object}
                  </span>
                )}
                {'ticket' in event ? (
                  <>
                    <span className="text-muted-foreground">to ticket</span>
                    <Tag
                      size="sm"
                      className="bg-card"
                    >
                      {event.ticket}
                    </Tag>
                  </>
                ) : null}
                <span className="text-muted-foreground">{event.time}</span>
              </div>
              {'comment' in event ? (
                <div className="rounded-control border bg-card px-4 py-3 text-sm text-foreground">
                  <Tag
                    size="sm"
                    className="border-transparent bg-palette-blue-soft text-palette-blue-soft-foreground"
                  >
                    {event.mention}
                  </Tag>{' '}
                  {event.comment}
                </div>
              ) : null}
            </div>
          </Timeline.Item>
        ))}
      </Timeline>
    </div>
  );
}

Installation

Add this component with the NateUI CLI.

npx nateui@latest add Timeline

Examples

Basic

Preview
Dark
  • Brief received

    Requirements were collected and attached to the project.

  • Design reviewed

    The team approved the main layout and edge cases.

  • Ready for build

    Implementation can start once the branch is created.

import Timeline from '@/components/ui/Timeline';

export default function BasicDemo() {
  return (
    <Timeline className="max-w-md">
      <Timeline.Item>
        <p className="font-medium text-foreground">Brief received</p>
        <p className="text-sm text-muted-foreground">
          Requirements were collected and attached to the project.
        </p>
      </Timeline.Item>
      <Timeline.Item>
        <p className="font-medium text-foreground">Design reviewed</p>
        <p className="text-sm text-muted-foreground">
          The team approved the main layout and edge cases.
        </p>
      </Timeline.Item>
      <Timeline.Item>
        <p className="font-medium text-foreground">Ready for build</p>
        <p className="text-sm text-muted-foreground">
          Implementation can start once the branch is created.
        </p>
      </Timeline.Item>
    </Timeline>
  );
}

Custom Media

Preview
Dark
  • Import completed

    Completed

    18,432 customer rows were validated.

  • QA review

    In review

    Two edge cases are waiting for product approval.

  • Launch handoff

    Queued

    Support notes are queued for the release channel.

import Timeline from '@/components/ui/Timeline';
import { PiCheck, PiClock, PiFlag } from 'react-icons/pi'

const STEPS = [
  {
    title: 'Import completed',
    description: '18,432 customer rows were validated.',
    status: 'Completed',
    icon: PiCheck,
    mediaClassName:
      'bg-palette-emerald-soft text-palette-emerald-soft-foreground',
  },
  {
    title: 'QA review',
    description: 'Two edge cases are waiting for product approval.',
    status: 'In review',
    icon: PiClock,
    mediaClassName:
      'bg-palette-blue-soft text-palette-blue-soft-foreground',
  },
  {
    title: 'Launch handoff',
    description: 'Support notes are queued for the release channel.',
    status: 'Queued',
    icon: PiFlag,
    mediaClassName:
      'bg-muted text-muted-foreground',
  },
] as const;

export default function CustomMediaDemo() {
  return (
    <Timeline className="max-w-xl">
      {STEPS.map((step) => {
        const Icon = step.icon;

        return (
          <Timeline.Item
            key={step.title}
            media={
              <span
                className={`flex size-6 items-center justify-center rounded-full border ${step.mediaClassName}`}
              >
                <Icon className="size-3.5" />
              </span>
            }
          >
            <div className="grid gap-2">
              <div className="flex flex-wrap items-center gap-2">
                <p className="font-medium text-foreground">
                  {step.title}
                </p>
                <span className="text-xs text-muted-foreground">
                  {step.status}
                </span>
              </div>
              <p className="text-sm text-muted-foreground">
                {step.description}
              </p>
            </div>
          </Timeline.Item>
        );
      })}
    </Timeline>
  );
}

API

Timeline renders a list of ordered entries and hides the connector after the last item. Timeline.Item renders the marker column plus the content column for one entry. The exported types extend native ul and li props, but the current implementation only forwards the props listed below.

Timeline

PropDescriptionTypeDefault
childrenItems rendered in sequence. Use Timeline.Item children so the final connector can be derived.ReactNode-
classNameExtra classes for the root ul.string-
refRef forwarded to the root ul.Ref<HTMLUListElement>-

Timeline.Item

PropDescriptionTypeDefault
childrenContent rendered beside the marker.ReactNode-
mediaCustom marker content. When omitted, the item renders the default dot.string | ReactNode-
isLastHides the connector and bottom padding. Usually derived by Timeline from item position.booleanDerived from position
classNameExtra classes for the root li.string-
refRef forwarded to the root li.Ref<HTMLLIElement>-