Components · Layout and structure

Timeline

A vertical sequence of milestones connected by a rail. Each item has a caption (when), a title (what), and a body (why). Reach for it on application flows, order tracking, and any chronological list where the relationship between steps is the content.

Documentedby Derek Fidler
  1. Today

    Easy application

    We just need a few final business details. You'll receive an offer based on your risk profile and transaction volume, and once you sign with our partner Liberis, your funding is already on its way to you.

  2. 5–8 business days

    Funds received

    Use your cash advance to fuel your next stage of growth, and pay it automatically through a fixed percentage of your daily sales.

Overview

The timeline lives in @flatpay-dk/ui as a single component that takes an items array. Each item carries an optional caption, a required title, an optional description, and a status — current, upcoming, or complete. The component renders a continuous rail behind a stack of nodes; it doesn't care whether the data comes from a static config (Capital flow) or a live feed (order state).

When the relationship is the content

Reach for Timeline when the order between items matters and the user benefits from seeing the chain. Static lists, checklists, or a flat set of cards are better served by List or Section.

States

Three node states cover every flow we've modelled. Set them per-item via status, or pass activeIndex on the timeline and the rail will derive each item's state from its position.

  1. Today

    Easy application

    Verify your business details — funding follows.

status — current

The active step. Light-blue node + deep-teal dot mark it as the user's place in the flow.

  1. 5–8 business days

    Funds received

    Cash advance is on its way once the application is signed.

status — upcoming

A step ahead of the user. Muted node + grey dot — visible but not pulling attention.

  1. 9 May

    Application signed

    Funding agreement countersigned by Liberis.

status — complete

A step the user has already cleared. Charcoal node + a check glyph — same vocabulary the rest of the system uses for done states.

  1. 9 May, 11:24

    Order placed

    We've received your order and queued it for fulfilment.

  2. 9 May, 14:02

    Packed

    Two terminals + a receipt printer are in the shipper's queue.

  3. 10 May, 08:30

    Shipped

    Tracking: PostNL 3SABCD123456789. ETA tomorrow.

  4. Estimated

    Delivered

    Hand-off to whoever signs at the merchant address.

activeIndex shorthand

When you don't want to set per-item status, pass `activeIndex` and the rail derives complete / current / upcoming from position.

Numbered nodes

When the timeline is also a numbered process — onboarding, a checklist with strict order — pass node on each item to render its index instead of the default dot. The wrapper still paints the surface, so colour stays in step with status.

  1. Step 1

    Connect your bank

    Link the account where your daily payouts will land.

  2. Step 2

    Verify your business

    Upload an ID document and confirm your Chamber of Commerce number.

  3. Step 3

    Sign your contract

    Final paperwork before funding is released.

Responsive

The timeline doesn't change shape on mobile — it's already a column. What changes is type and node sizing: title drops from 24 px → 20 px, node from 48 px → 40 px, gap from 24 px → 16 px. The rail stays continuous; the data the user reads stays the same.

Type & node sizing
360 px
Below sm (640 px): title tightens to 20 px, node shrinks to 40 px, gap closes to 16 px. No layout switch — the timeline is already a column.

Anatomy

Two columns. The rail handles wayfinding; the text column carries the content.

PartRole
Rail1 px line in `border-border` running behind a stack of 48 px (40 px on mobile) circular nodes. The line stops at the centre of the last node so it doesn't trail off into empty space.
NodeCarries the status surface. `current` = light blue with a deep teal dot; `upcoming` = muted neutral with a quiet inner dot; `complete` = filled charcoal with a check glyph. Override the centre via `node` for numbered steps.
CaptionInter Tight Medium 14 px / muted-foreground. The eyebrow that anchors the step in time — a duration ('5–8 business days'), a date, or a milestone label like 'Today'.
TitleInter Tight Bold 24 px / 20 px on mobile. The single most prominent thing in the row.
DescriptionInter Tight Regular 16 px / muted-foreground. Capped at 65ch via `max-width` so long body text wraps before fatigue.

Accessibility

The component renders an ordered list (<ol>) so screen readers announce the sequence and the position of each item. Nodes and the rail line are decorative — they're marked aria-hidden so the assistive-tech outline stays focused on the meaningful content. The status palette is reinforced by copy (the caption tells the user when the step happens) so meaning never lives only in colour.

Code

Capital flow (the canonical case)

tsx

import { Timeline } from "@flatpay-dk/ui";

<Timeline
  items={[
    {
      caption: "Today",
      title: "Easy application",
      description:
        "We just need a few final business details. You'll receive an offer based on your risk profile and transaction volume.",
      status: "current",
    },
    {
      caption: "5–8 business days",
      title: "Funds received",
      description:
        "Use your cash advance to fuel your next stage of growth.",
      status: "upcoming",
    },
  ]}
/>;

Order tracking (with `activeIndex`)

tsx

<Timeline
  activeIndex={2}
  items={[
    { caption: "9 May, 11:24", title: "Order placed" },
    { caption: "9 May, 14:02", title: "Packed" },
    { caption: "10 May, 08:30", title: "Shipped" },
    { caption: "Estimated", title: "Delivered" },
  ]}
/>;

Numbered onboarding

tsx

<Timeline
  items={[
    { title: "Connect your bank", status: "complete", node: <>1</> },
    { title: "Verify your business", status: "current", node: <>2</> },
    { title: "Sign your contract", status: "upcoming", node: <>3</> },
  ]}
/>;

Best practices

  1. Today

    Easy application

    Verify your details.

Do

Use the caption to anchor the step in time. 'Today', '5–8 business days', or a real date — give the user something they can plan around.

  1. Easy application

    Verify your details.

Don't

Don't ship a timeline without anchoring captions. The rail implies sequence; the captions tell the user what 'next' actually means.

  1. Today

    Easy application

    Two pages of business details, then sign.

Do

Keep descriptions short. The timeline is a wayfinding surface — long copy here drowns the rail's signal.

  1. Today

    Easy application

    We just need a few final business details. You'll receive an offer based on your risk profile and transaction volume, and once you sign with our partner Liberis, your funding is already on its way to you. The full underwriting process takes between 24 and 72 hours and we may reach out for clarification depending on your business type.

Don't

Don't paste an entire policy paragraph into a description. If the step needs that much explanation, it belongs on its own page.

Props

PropTypeDefaultDescription
items*TimelineItem[]The ordered set of milestones.
activeIndexnumberConvenience: items before this index render as `complete`, the one at the index as `current`, the rest as `upcoming`. Per-item `status` overrides this.
classNamestringForwarded to the wrapper <ol>.

TimelineItem

PropTypeDefaultDescription
title*stringHeadline. Set in 24 px bold (20 px on mobile).
captionstringEyebrow above the title — a date or duration. Drives wayfinding.
descriptionReactNodeBody copy. Keep it to 1–3 sentences.
status"current" | "upcoming" | "complete""upcoming"Per-item visual state. Overrides the timeline's `activeIndex` derivation.
nodeReactNodeCustom node content (e.g. a step number or icon). The wrapper still applies the status surface; pass content that inherits via currentColor.