Moltenship Time

Quick start

Build a week, day, month, and agenda calendar with the built-in views.

You'll build this calendar: a toolbar, a view switcher, and the built-in week, day, month, and agenda views, showing your own meetings.

Live, in your time zone. Drag to move, resize or create events.

To see these views running with more data before you start, open the live demo.

Install

Install @moltenship/calendar as described in Installation, including Temporal if your browsers need it.

Copy the UI source

The views are source files that you copy into your app. They're built on shadcn/ui and its default Base UI primitives, so your app needs a Base UI style (see UI source). Add the primitives they use:

npx shadcn@latest add button popover select tooltip

Then copy these folders from the repository's registry, and change their @registry/… imports to your own paths, as UI source describes:

FolderCopy toContains
calendar-themestyles/, components/calendar/The theme CSS and event color names
calendarcomponents/calendar/Toolbar, view switcher, event chip
time-grid-viewcomponents/calendar/time-grid-view/Week and day views
month-viewcomponents/calendar/month-view/Month view
list-viewcomponents/calendar/list-view/Agenda view

Import calendar-theme.css in your global CSS, after the Tailwind and shadcn imports.

Create the calendar

Describe your records with an adapter, then create a calendar with the views you want. The first view in the list is shown first.

calendar.tsx
"use client";

import { createCalendar, defineEventAdapter } from "@moltenship/calendar";

import { CalendarToolbar } from "@/components/calendar/calendar-toolbar";
import type { CalendarEventColor } from "@/components/calendar/event-colors";
import { createAgendaView } from "@/components/calendar/list-view/agenda-view";
import { createMonthView } from "@/components/calendar/month-view/month-view";
import { createDayView, createWeekView } from "@/components/calendar/time-grid-view/time-grid-view";
import { ViewSwitcher } from "@/components/calendar/view-switcher";

export interface Meeting {
  readonly id: string;
  readonly title: string;
  readonly start: Temporal.Instant;
  readonly end: Temporal.Instant;
  readonly color: CalendarEventColor;
}

export const eventAdapter = defineEventAdapter<Meeting>()({
  getId: (meeting) => meeting.id,
  getTitle: (meeting) => meeting.title,
  getInterval: (meeting) => ({ kind: "timed", start: meeting.start, end: meeting.end }),
  getResourceIds: () => [],
});

const getEventColor = (meeting: Meeting) => meeting.color;

export const Calendar = createCalendar<Meeting>()({
  eventAdapter,
  views: [
    createWeekView<Meeting>({ dayView: "day", getEventColor }),
    createDayView<Meeting>({ getEventColor }),
    createMonthView<Meeting>({ getEventColor }),
    createAgendaView<Meeting>({ getEventColor }),
  ],
});

getEventColor picks each event's color from the palette. dayView: "day" makes the dates in the week header open the day view.

Render it

Root holds the calendar state. It needs your events, a starting date, a time zone, a locale, and week rules (the first day of the week, where 1 is Monday, and the weekend days). CalendarToolbar adds the Today, previous, and next buttons and the title, and the registry's ViewSwitcher switches between your views.

calendar.tsx
const viewLabels = { week: "Week", day: "Day", month: "Month", agenda: "Agenda" };
const viewSwitcherOptions = Calendar.views.map(({ name }) => ({
  value: name,
  label: viewLabels[name],
}));

function CalendarViewSwitcher() {
  const view = Calendar.useCalendarSelector((state) => state.view);
  const { setView } = Calendar.useCalendarActions();
  return <ViewSwitcher value={view} onValueChange={setView} options={viewSwitcherOptions} />;
}

export const weekRules = { firstDay: 1, minimalDays: 4, weekend: [6, 7] } as const;

export function TeamCalendar({ meetings }: { readonly meetings: readonly Meeting[] }) {
  return (
    <Calendar.Root
      events={meetings}
      defaultAnchorDate={Temporal.PlainDate.from("2026-10-08")}
      defaultTimeZone="Europe/Berlin"
      locale="en-US"
      weekRules={weekRules}
    >
      <Calendar.Frame className="flex h-[640px] flex-col rounded-xl border">
        <CalendarToolbar calendar={Calendar}>
          <CalendarViewSwitcher />
        </CalendarToolbar>
        <Calendar.Viewport className="min-h-0 flex-1">
          <Calendar.ActiveView />
        </Calendar.Viewport>
      </Calendar.Frame>
    </Calendar.Root>
  );
}

Give the frame a height: the views fill it and scroll inside it. The calendar is read-only until you add an onEventChange handler; Interactions shows how to save dragged and resized events.

The toolbar's buttons use shadcn's Tooltip, which shadcn sets up with a TooltipProvider around your app. If your app doesn't have one yet, add it once:

app.tsx
import { TooltipProvider } from "@/components/ui/tooltip";

<TooltipProvider>
  <TeamCalendar meetings={meetings} />
</TooltipProvider>;

Using Next.js?

Calendar modules are Client Components, which is why the file starts with "use client". See Next.js and server rendering for passing data from a Server Component.

Next steps

Edit on GitHub

Last updated on

On this page