Data Storytelling

Event Bar Chart

A Gantt-like event bar chart React component with timed segments on swimlanes, panned by drag or arrow keys, an event card that expands on hover or focus, a self-contained SVG time axis and a screen reader data table.

Built for schedules, release plans and project milestones — any "who does what, when" data. A fixed lane label column sits on the left and a pannable time canvas on the right: the axis switches between day, week and month ticks based on the visible span, each event is a rounded bar positioned by its start and end days, and bars too narrow for their label keep only the color. Press and drag the canvas to pan, with inertia after you let go; Shift + wheel or horizontal trackpad scrolling pans too, and the arrow buttons in the top corners move a third of the view at a time. Hovering or focusing a bar lifts it and expands an event card (date range, day count, status and details); clicking pins the card, and clicking again or pressing Esc closes it.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add event-bar-chart.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

30
40
180
Active edited usageDefault props
<EventBarChart/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/event-bar-chart.en.json

Or, once registries are configured in components.json, install it as @webberui/event-bar-chart.

Usage

import { EventBarChart } from "@/components/ui/event-bar-chart";

<EventBarChart
  lanes={[
    { id: "design", label: "Design" },
    { id: "web", label: "Frontend", color: "#3b82f6" },
  ]}
  events={[
    {
      id: "d1",
      lane: "design",
      start: "2026-10-03",
      end: "2026-10-18",
      label: "Design review",
      status: "active",
      detail: "High-fidelity mocks of the home and checkout screens.",
    },
    { id: "w1", lane: "web", start: "2026-10-12", end: "2026-10-30", label: "Checkout redesign", status: "planned" },
  ]}
  visibleSpan={30}
  onSelectedChange={(id) => console.log(id)}
/>

Props

PropTypeDefaultDescription
lanesEventBarLane[]—Lanes, listed top to bottom
eventsEventBarEvent[]—Events; Tab order follows start time, not array order
domainEventBarDomainEvent range plus a 5% marginBrowsable time range; panning is clamped to it
visibleSpannumber30Number of days visible at once; a shorter range fills the canvas
selectedIdstring | null—Controlled selected (pinned card) event id; managed internally when omitted
defaultSelectedIdstring | nullnullInitial selected event in uncontrolled mode
onSelectedChange(id: string | null) => void—Called when the selection changes; clicking again, Esc or the close button returns null
todaystring | number | nullUser's local dateThe "today" date; read after mount when omitted, and null hides it
showTodaybooleantrueWhether to show the vertical "today" line
laneHeightnumber40Height of each lane (px)
ariaLabelstring"事件區段時間軸" (event timeline)Accessible name of the chart, used for the summary and the table caption
classNamestring—Passed through to the outermost container

EventBarLane

FieldTypeDefaultDescription
idstring—Unique id that events reference through lane
labelstring—Lane name
colorstringAccent variableBar color for this lane (any valid CSS color)

EventBarEvent

FieldTypeDefaultDescription
idstring—Unique id
lanestring—Lane id; events with no matching lane are not drawn
startstring | number—Start day: an ISO date string or a timestamp in ms
endstring | number—End day (inclusive)
labelstring—Event name, shown in the bar and as the card title
detailstring—Extra description shown in the event card
status"done" | "active" | "planned"—Status: done (tinted with a left edge), active (solid), planned (dashed outline)

EventBarDomain

FieldTypeDefaultDescription
startstring | number—First day of the browsable range
endstring | number—Last day of the browsable range (inclusive)

formatEventRange(start, end) is also exported by name. It returns a string like "10/3–10/18 · 16 days", so readouts outside the chart can reuse the same format.

How it works

  • Dates are always read as UTC calendar days: browsers parse ISO date strings as UTC, so reading month and day in local time would make the server and a UTC+8 user disagree. end is therefore inclusive, and a bar's right edge reaches midnight of the next day.
  • Ticks: day ticks up to 14 visible days, week ticks (on Mondays) up to 70, month ticks beyond that; the interval then widens based on pixels per day so labels never collide.
  • The initial view puts today at the left third of the visible width, or starts at the beginning when today is out of range. Keyboard focus on an off-screen bar pans to it automatically; mouse clicks never jump the view.
  • A pointer has to move more than 4px to count as a drag, and only then is it captured, so tapping a bar is still a normal click. After a drag, the canvas coasts on the release velocity (power 0.6) and bounces softly at the edges.
  • The accent color comes from --wb-event-bar-chart-accent (defaults to #0d9488 in light mode and #2dd4bf in dark mode) and applies to every lane without its own color; the card open duration comes from --wb-event-bar-chart-duration (default 180ms).
  • Events that overlap in time within the same lane are drawn on top of each other; split them into two lanes if needed.

Accessibility

  • The time canvas is focusable (role="group"): ← / → pan, Home returns to today and End jumps to the end. The SVG axis is role="img" with an aria-label summarizing lane count, event count and range, plus aria-describedby pointing at an sr-only data table
  • Every event bar is a native button; Tab moves through them in time order, the aria-label reads like "Design review, Oct 3 to Oct 18, In progress", and aria-pressed reflects whether it is pinned
  • The event card is role="tooltip" on hover or focus and becomes role="dialog" with a close button once pinned; Esc closes it
  • When the system "reduce motion" setting is on, bars appear at full size without the grow animation, panning jumps instantly with no inertia or spring, and the event card only fades in