KQ-1744 · LEAVE/PLANNER

Leave Planner

A time-off request surface: a balance meter of the leave type's days over one to three months of calendar, with teammates already away drawn as soft overlapping stripes on their dates. Choosing a range drains the meter a day at a time — one motion value walking about 45 ms a day on the linear ease — while the count of working days rolls and each working day in the calendar lights in the same beat; weekends and holidays inside the range stay hatched because they are skipped, the type switch slides on springs.snap and the months glide in on springs.glide. Asking for more than the balance runs the meter dry, shakes it once on a tween and explains the shortfall; each month is a grid with arrow keys, Enter for the first and last day, Shift to extend and Escape to clear.

KQ-1744 · LEAVE/PLANNER

Time off

Fernworks studio · 4 teammates

Arrow keys move between days, Page Up and Page Down between months. Enter sets the first day and then the last; Shift with an arrow extends the selection; Escape clears it.

9days left of 25

16 taken9 left

Choose the first day off

November 2026

MTWTFSS

Also away

  • Ines Moreau
  • Theo Park
  • Sam Okoro
  • Priya Nair

Choose days on the calendar to request them.

annual · 9 left

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/leave-planner

Props

stripeStyle
"bands" | "hatch" | "dots"
default "bands"
How teammates' leave is drawn: bands that join across consecutive days, diagonal hatching that crosses where two overlap, or stacked dots.
monthCount
number
default 2
Months side by side on a wide container, 1 to 3; below a 34rem container it is always one.
tone
"cobalt" | "moss" | "ember" | "plum"
default "cobalt"
The colour of your request and balance, as a pigment at a fixed lightness.
policies
LeavePolicy[]
default defaultLeavePolicies
The kinds of leave: { id, label, allowance?, noticeDays? }; no allowance means no cap.
balance
Record<string, number>
default defaultLeaveBalance
Days left of each kind, by policy id.
holidays
LeaveHoliday[]
default defaultLeaveHolidays
Public holidays ({ date, name }); never counted as leave.
team
LeaveTeammate[]
default defaultLeaveTeam
Teammates and the spans they are already away: { id, name, away: { from, to }[] }.
requests / defaultRequests
LeaveRequest[]
default — / []
Requests already made ({ id, type, from, to, days }); their days are held off the balance. Controlled or initial.
onRequestsChange
(requests) => void
default —
Fires from Request and Cancel with every request.
type / defaultType
string
default — / the first policy
The kind of leave, by policy id; controlled or initial.
onTypeChange
(type) => void
default —
Fires from the press or arrow key that changed the kind of leave.
range / defaultRange
{ from, to } | null
default — / null
The selection, ms at UTC midnight, inclusive; controlled or initial.
onRangeChange
(range) => void
default —
Fires from the press, drag or key that changed the selection.
onRequest
(request) => void
default —
Request was pressed, with the request and its working days.
onCancel
(request) => void
default —
Cancel was pressed on a request.
now
number
default defaultLeaveNow
Today in ms; earlier days cannot be chosen and notice counts from it. Never read from the clock.
defaultMonth
number
default the month after now
The first month shown: any ms inside it.
status
"ready" | "loading" | "error"
default "ready"
Loading shows a placeholder month; error explains and offers Try again.
onRetry
() => void
default —
Try again was pressed after the calendar failed to load.
title
string
default "Time off"
The heading.
height
number
default 540
Height of the surface in px; the body scrolls inside it.
disabled
boolean
default false
Read only: nothing can be chosen or requested.
className
string
default —
Classes for the surface.