Calendar
A demo appointments calendar at /calendar with three views: Month, Week, and Day. It has an AM/PM hour grid with a live current-time line, event blocks that span their own duration, Google-style color labels, and a clean add dialog, all built from Dialog, Input, Select, Checkbox and date-fns. No calendar dependency, no hand-rolled table, no custom time picker.
See it live
Open /calendar in the app. Switch views with the segmented control (top-right), click any day (Month) or hour cell (Week/Day) to add, and click an event to remove it.
Views
Week and Day render AM/PM hour labels (9 AM) and a red current-time line, and they auto-scroll the current hour into view when opened. Each event is positioned by its start time and sized by its duration. When two events overlap they split into side-by-side columns: a lane-assignment pass groups them into clusters and divides the width evenly.
Event model
Events carry a start and end so Week/Day can position a block by its start and size it by its duration.
type Ev = {
id: number;
date: string; // "yyyy-MM-dd"
start: string; // "HH:mm"
end: string; // "HH:mm"
title: string;
color: string; // color-label key (see below)
type: "event" | "task" | "appointment";
guests?: string;
meet?: boolean;
location?: string;
description?: string;
notify: string; // minutes before, as string
};Color labels
A Google-style palette: Blueberry, Tomato, Tangerine, Banana, Sage, Peacock, Lavender, Grape, and Graphite. Because these are content colors, they use static Tailwind classes, the same convention as the tab color labels (TAB_COLORS). The color you pick drives the event block, its chip, and a live dot beside the title.
const EVENT_COLORS = [
{ key: "blueberry", label: "Blueberry", chip: "bg-blue-600 text-white", dot: "bg-blue-600" },
{ key: "tomato", label: "Tomato", chip: "bg-red-600 text-white", dot: "bg-red-600" },
// …tangerine, banana, sage, peacock, lavender, grape, graphite
];Add dialog
Simple by default, complete on demand. The essentials stay visible; everything else tucks behind More options.
Always visible
Behind “More options”
No native time picker
Select with AM/PM options rather than <input type="time">. It looks the same in every browser, with no native clock panel to fight. If the end isn't after the start, it auto-corrects to start + 1h. Built from
Dialog, Input, Select, Checkbox, Button, Breadcrumbs, cn, and date-fns. A copy-ready brief for it lives in the requirement templates.