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.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<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.jsonOr, 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
| Prop | Type | Default | Description |
|---|---|---|---|
lanes | EventBarLane[] | — | Lanes, listed top to bottom |
events | EventBarEvent[] | — | Events; Tab order follows start time, not array order |
domain | EventBarDomain | Event range plus a 5% margin | Browsable time range; panning is clamped to it |
visibleSpan | number | 30 | Number of days visible at once; a shorter range fills the canvas |
selectedId | string | null | — | Controlled selected (pinned card) event id; managed internally when omitted |
defaultSelectedId | string | null | null | Initial selected event in uncontrolled mode |
onSelectedChange | (id: string | null) => void | — | Called when the selection changes; clicking again, Esc or the close button returns null |
today | string | number | null | User's local date | The "today" date; read after mount when omitted, and null hides it |
showToday | boolean | true | Whether to show the vertical "today" line |
laneHeight | number | 40 | Height of each lane (px) |
ariaLabel | string | "事件區段時間軸" (event timeline) | Accessible name of the chart, used for the summary and the table caption |
className | string | — | Passed through to the outermost container |
EventBarLane
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique id that events reference through lane |
label | string | — | Lane name |
color | string | Accent variable | Bar color for this lane (any valid CSS color) |
EventBarEvent
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique id |
lane | string | — | Lane id; events with no matching lane are not drawn |
start | string | number | — | Start day: an ISO date string or a timestamp in ms |
end | string | number | — | End day (inclusive) |
label | string | — | Event name, shown in the bar and as the card title |
detail | string | — | Extra description shown in the event card |
status | "done" | "active" | "planned" | — | Status: done (tinted with a left edge), active (solid), planned (dashed outline) |
EventBarDomain
| Field | Type | Default | Description |
|---|---|---|---|
start | string | number | — | First day of the browsable range |
end | string | 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.
endis 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#0d9488in light mode and#2dd4bfin dark mode) and applies to every lane without its owncolor; the card open duration comes from--wb-event-bar-chart-duration(default180ms). - 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 isrole="img"with anaria-labelsummarizing lane count, event count and range, plusaria-describedbypointing at an sr-only data table - Every event bar is a native
button; Tab moves through them in time order, thearia-labelreads like "Design review, Oct 3 to Oct 18, In progress", andaria-pressedreflects whether it is pinned - The event card is
role="tooltip"on hover or focus and becomesrole="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
Balance Chart
A diverging bar chart React component centred on a zero axis: income grows up and expenses grow down, a net line draws in once the bars land, and hover or keyboard focus shows the period's income, expenses and net.
Stacked Column Chart
A stacked column chart React component. Clicking a legend item collapses that layer while the remaining layers sink into place on a spring and the Y axis rescales with tweened ticks; hover or use the arrow keys for a per-column breakdown.