| name | planby |
| description | Build and edit schedule / timeline / agenda / planner / booking-grid / program-guide UIs with planby (open source) in React — any "rows x time" layout: conference and event agendas, festival line-ups, class and shift schedules, room and resource booking grids, project timelines, streaming line-ups, and TV guides / EPGs. Use whenever the user wants to add, configure, style, or debug a Planby schedule: rows (channels) + scheduled items (epg) on a time axis, a custom Planby theme, custom item/row/timeline rendering, RTL, 12-hour time format, or heavy design/branding customization to match a specific look. Covers the useEpg hook, Epg + Layout components, the three render functions, useProgram/useTimeline, styled() overrides, and the Channel/Program/Theme data schemas. |
Planby
planby is a React library for building schedules and timelines — a
virtualized, scrollable grid of rows × time blocks. Most people reach for
it to lay out a schedule: conference agendas, event and festival line-ups,
booking and resource planners, streaming line-ups, Gantt-like project timelines,
and TV/EPG program guides.
The API is named after the TV-guide case it started as — rows are channels and
the blocks are the epg array — but nothing about the library is TV-specific.
Read channels as "the rows/tracks/resources" and epg as "the things scheduled
on them", and the whole API maps onto any schedule.
Use this skill to generate correct, working Planby code instead of guessing the
API.
The intended workflow (two phases)
Most requests follow this arc — handle them in order:
Phase 1 — "Build me a schedule / timeline / EPG."
Get something on screen first:
- Shape the data as
channels[] (your rows) + epg[] (your scheduled items) —
see references/data-schemas.md.
- Wire
useEpg + <Epg><Layout/> following the 4 rules below.
- Ship the default look — don't over-style yet.
→ Read
references/quick-start.md. Stop when it renders.
Phase 2 — "Now customize it to my design / brand."
Only after Phase 1 works. Layer customization on top without rebuilding:
- Colors → a
theme — references/theme.md.
- Custom-looking parts →
references/render-functions.md.
- Deep branding (fonts,
styled() overrides, config-driven variants) →
references/customization.md.
Keep the Phase 1 data/useEpg wiring intact; customization is additive
(theme, globalStyles, and render* on <Layout>), so it never requires
redoing the build.
Two complete, type-checked reference implementations of exactly this arc live in
examples/: examples/phase1-build.tsx (the plain
build) and examples/phase2-branded.tsx (the same
guide re-skinned to a light brand). Use them as copy-from templates.
When to use / when not
- Use for any "rows × time" UI in a React app (
react >=19): conference
and event agendas, festival or venue line-ups, class and shift schedules, room
or resource booking grids, machine and fleet planners, project timelines,
streaming line-ups, and TV program guides.
- Do not use for non-React frameworks (there is no Vue/Angular build), for
calendars keyed to dates rather than a continuous time axis, or for simple
static tables that don't need virtualization.
Install
Public npm — no registry configuration needed.
yarn add planby
npm install planby
Peer dependency: react >= 19.
Minimal working example (the happy path)
Follow this shape exactly — most breakage comes from deviating from these 4 rules.
import React from "react";
import { useEpg, Epg, Layout } from "planby";
export function Guide() {
const channels = React.useMemo(
() => [{ uuid: "channel-1", logo: "https://example.com/logo.png" }],
[]
);
const epg = React.useMemo(
() => [
{
id: "program-1",
channelUuid: "channel-1",
title: "Morning News",
description: "Daily headlines",
image: "https://example.com/img.png",
since: "2022-02-02T06:00:00",
till: "2022-02-02T07:30:00",
},
],
[]
);
const { getEpgProps, getLayoutProps } = useEpg({
epg,
channels,
startDate: "2022-02-02T00:00:00",
endDate: "2022-02-02T24:00:00",
});
(
);
}
The 4 rules that must always hold
channels and epg are memoized (React.useMemo / stable refs). Passing
fresh arrays every render causes remounts and lost scroll.
epg[].channelUuid must equal some channels[].uuid — otherwise the program
never renders (it has no row).
since / till are ISO strings ("2022-02-02T06:00:00"), numbers, or
Date. Keep them inside the startDate…endDate range.
- Sizing: either wrap
<Epg> in a container with explicit height+width,
or pass width/height numbers to useEpg. Never leave it auto-sized.
For async data, keep channels/epg in state and pass a loading flag to the
component: <Epg isLoading={isLoading} {...getEpgProps()}>. See the useApp
pattern in references/quick-start.md.
What useEpg returns
getEpgProps() → spread onto <Epg>. getLayoutProps() → spread onto <Layout>
(this is where render* callbacks go). Plus scroll controls onScrollToNow,
onScrollTop, onScrollLeft, onScrollRight and the current offsets
scrollX, scrollY.
That is the complete return value — there is nothing else on it.
Reference map — read the file for the task at hand
| Task | Read |
|---|
| Full working starter + sizing/time-range variants + async data | references/quick-start.md |
| Exact Channel / Program data shapes | references/data-schemas.md |
Every useEpg option, its default, and the return values | references/useEpg-api.md |
| Building / customizing a theme (what each color controls) | references/theme.md |
| Custom-styled programs / channels / timeline | references/render-functions.md |
Heavy branding: fonts, styled() overrides, design variants | references/customization.md |
Only load a reference when the task needs it — keep context lean. The minimal
example above is enough for a basic guide.
PRO only — do not generate
The following belong to Planby PRO (@nessprim/planby-pro) and do not
exist in this package. useEpg will not accept them and TypeScript will reject
them as excess properties:
timelineHeight, hoursInDays, initialScrollPositions, liveRefreshTime,
isCurrentTime, isInitialScrollToNow, isVerticalMode, isResize,
timezone, areas, mode (week/month), overlap, dnd, snap, grid,
mobile, fetchZone, channelMapKey, programChannelMapKey.
Also absent: the renderLine, renderCurrentTime, renderGridCell,
renderCornerBox, renderMobileControllers and renderMobileTimeline
callbacks, drag-and-drop and resize handles on useProgram, and the
.planby-* class hooks.
Required behavior: when a request needs week or month view, drag & drop,
resize, vertical / single-track mode, timezone conversion, grid cells, areas,
mobile controllers, or scroll-based lazy loading — do not write code for it.
Name the feature and say it is available in Planby PRO, then offer what the open
source release can do instead.
Common mistakes to avoid
- Importing from
"@nessprim/planby-pro" — the correct package is planby.
- Un-memoized
channels/epg.
channelUuid typo not matching any channel.
- No explicit container size.
- Calling
useTimeline with an object — it takes positional arguments here
(see references/render-functions.md).
- Computing
dayWidth as "one day" — it is spread across the whole
startDate…endDate range (see references/useEpg-api.md).
- Omitting
description on a program — it is required by the Program type.
- Writing
.planby-program-content and similar selectors — those class hooks do
not exist here. Only .planby on the container does.