Skip to content

Time Lanes

Show shifts or runs in lanes across a week or a day, with overrides, proposals, handoffs and now, such as an on-call rota.

import { RotaLane, RotaShift, TimeLanes } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use time lanes for any rotation where the question is who holds it when: on-call pagers, support shifts, a release captain. There's a lane per person or team, each shift a bar from its start to its end, an override hatched over the lane of whoever covers, a dashed baton where one lane hands off to another, and now as a line in the accent. For events that already happened, use a Timeline; for jobs that run on a daily clock, a Day Ring.

import { TimeLanes, type RotaLane, type RotaShift } from '@youj/react'

const lanes: RotaLane[] = [
  { id: 'mara', name: 'Mara Quinn', detail: 'Platform' },
  { id: 'theo', name: 'Theo Park', detail: 'Platform' },
]
const shifts: RotaShift[] = [
  { id: 'a', lane: 'mara', start: new Date('2026-09-28T09:00'), end: new Date('2026-09-29T09:00') },
  { id: 'b', lane: 'theo', start: new Date('2026-09-29T09:00'), end: new Date('2026-09-30T09:00') },
  { id: 'c', lane: 'theo', start: new Date('2026-09-28T13:00'), end: new Date('2026-09-28T17:00'), override: true, note: 'Covering Mara' },
]

<TimeLanes lanes={lanes} shifts={shifts} start={new Date('2026-09-28T00:00')} />

start is the first moment shown (usually a Monday at midnight) and days how many follow (7 by default). A shift sits in its lane from start to end; override draws it hatched above the lane, and note adds a line to its detail. Lanes take the chart colors in turn, or their own tone. Where one lane's shift ends as another lane's begins, a dashed baton joins them, with a diamond on the day axis.

now places the line: by default it follows the clock, read every 30 seconds once the page is live (on the server there's no line until the browser has the time); pass a time to pin it, or null to hide it. Shifts that ended fade and the one on call now is lit. renderDetail adds your own actions to a shift's detail: Swap, Page, a link to the schedule.

Reference

Propslanes, shifts, start… (9)

Props

TimeLanes props
PropType
lanesRequiredRotaLane[]–
shiftsRequiredRotaShift[]–
startRequiredDate | number

The first moment shown: usually a Monday at midnight.

–
daysnumber

Days shown.

default 7

7
hoursnumber

Hours shown instead of days, for a day view ("08:00 to 17:00" is 9): the axis counts hours.

–
nowDate | number | null

Where the now line sits. Default: the clock, read every 30 s once the page is live.

–
labelstring

The rota's accessible name.

default "On-call rota"

"On-call rota"
localestring

Default: the YoujProvider locale.

–
renderDetail(shift: RotaShift, lane: RotaLane) => ReactNode

Extra content at the foot of a shift's detail: actions, links.

–
Other props pass through: extends ComponentProps<'div'>.

Shifts

RotaShift props
PropType
idRequiredstring–
laneRequiredstring

The lane it sits in.

–
startRequiredDate | number–
endRequiredDate | number–
overrideboolean

Covering someone else's shift: drawn hatched.

–
notestring

A line for the detail: "Covering Ada's dentist visit".

–
proposedboolean

Suggested, not yet accepted: drawn as a dashed outline in the accent (or its tone), never on call.

–
labelstring

Text on the bar in place of its start time: a job or load ("48291"). Read before the lane's name.

–
toneRotaTone
'chart-1''chart-2''chart-3''chart-4''chart-5''accent''danger''neutral'

The bar's own color, over its lane's: danger for a late run.

–
StylingParts and data attributes

Styling

Parts: time-lanes, time-lanes-heads, time-lanes-head, time-lanes-avatar, time-lanes-body, time-lanes-axis, time-lanes-day (with data-today), time-lanes-hour, time-lanes-handoff, time-lanes-grid, time-lanes-baton, time-lanes-lane, time-lanes-shift (with data-state, data-override and data-proposed), time-lanes-label, time-lanes-detail, time-lanes-when and time-lanes-now. A lane is --rota-lane tall (2.75rem).

MotionClock tick, detail · reduced motion

Motion

Motion tokens
MomentTokens
Clock tickthe now line glides to its new place
duration.slowease.standard

340ms · cubic-bezier(0.2, 0, 0, 1)

340mscubic-bezier(0.2, 0, 0, 1)
Detailthe shift’s popover opens from the bar
duration.fastease.enter

140ms · cubic-bezier(0.16, 1, 0.3, 1)

140mscubic-bezier(0.16, 1, 0.3, 1)

Reduced motion

The line jumps; the popover appears in place.

Reduced motion

The now line jumps to each new time, and the detail opens in place.

AccessibilityKeyboard

Accessibility

  • The rota is a group named by label (default: the labels' "On-call rota") and described by how to move through it; each lane is a group named by its person or team.
  • Each shift is a button named by its label, who, from and until when, whether it's an override and whether it's proposed ("4935, Iris Okafor, Mon 10:30 to Mon 13:00, Proposed").
  • The shifts are one tab stop, starting on the shift on call now. Left and right move along a lane (mirrored in RTL), up and down to the nearest shift in the next lane, Home and End to a lane's ends, and Enter or Space opens the detail.
  • The detail says when, for how long, whether it's an override, who takes over, and whether the shift is on call now, upcoming or ended.
  • Its words come from the labels (rota, rotaShift, rotaHint, rotaHandoff, rotaOverride, rotaOnCall, rotaUpcoming, rotaEnded, proposed), translated by the language packs.