npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@typescript-calendar-lib/react

v0.5.3

Published

React 19 calendar components with CSS themes and color schemes

Readme

@typescript-calendar-lib/react

A <Calendar /> React component with CSS-based themes and color schemes, plus a useCalendarState hook for full interactivity. The component is fully controlled — you pass year and month and it renders the grid.

Warning: Peer dependency — Requires React 19. The package doesn't bundle React; install it in your app:

npm install react @typescript-calendar-lib/react

Installation

pnpm add @typescript-calendar-lib/react
# or
npm install @typescript-calendar-lib/react
# or
bun add @typescript-calendar-lib/react

Quick Start

import { Calendar } from "@typescript-calendar-lib/react";
import "@typescript-calendar-lib/react/calendar.css"; // required — component styles

export function App() {
  return (
    <Calendar
      year={2026}
      month={9}
      colorScheme="ocean"
      theme="modern"
    />
  );
}

Server-side rendering

Calendar and InteractiveCalendar do not access browser APIs while rendering, so they can be rendered on the server:

import { renderToString } from "react-dom/server";
import { Calendar } from "@typescript-calendar-lib/react";

const html = renderToString(
  <Calendar year={2026} month={9} today={new Date(2026, 8, 15)} />,
);

In the Next.js App Router, a Server Component renders the calendar on the server by default:

// app/page.tsx
import { Calendar } from "@typescript-calendar-lib/react";

export default function Page() {
  return <Calendar year={2026} month={9} />;
}

Use a Client Component when the calendar needs browser interaction:

// app/calendar-panel.tsx
"use client";

import { InteractiveCalendar } from "@typescript-calendar-lib/react";

export function CalendarPanel() {
  return <InteractiveCalendar initialYear={2026} initialMonth={9} />;
}

Component API

Calendar

<Calendar year={2026} month={9} />

| Prop | Type | Default | Description | | :--- | :--- | :--- | :--- | | year | number | — (required) | Calendar year | | month | number | — (required) | Month, 1-indexed (1–12) | | locale | "en" \| "ja" \| "es" \| "de" \| "fr" \| "ko" \| "zh" | "en" | Language | | weekStart | "sunday" \| "monday" | "sunday" | First day of the week | | highlight | Date | — | Date to highlight | | range | { from: Date; to: Date } | — | Dates to emphasize | | rangePreview | { from: Date; to: Date } | — | Candidate range preview (hover preview), rendered with the is-in-range-preview class | | today | Date | — | Reference date for "today" styling | | theme | ThemeName \| ReactTheme | "default" | CSS class-based theme | | colorScheme | ColorSchemeName \| ReactColorScheme | "default" | CSS variable-based colors | | size | CalendarSize | "md" | Cell size | | responsive | boolean | false | Shrink cells & padding below 480px so the grid fits phone screens | | showWeekNumbers | boolean | false | Render a leading week-number column (th[scope="row"] with class calendar-week); the number follows weekStart ("monday" → ISO week, "sunday" → week-containing-Jan-1 week) | | style | CSSProperties | — | Extra styles for the root element | | interactive | boolean | false | Enable cell click/hover/keyboard selection | | onDateClick | (date: Date, data?: unknown) => void | — | Called when a day cell is clicked (or Enter/Space pressed); data is that cell's cellData value (undefined when none) | | onDateHover | (date: Date) => void | — | Called when a day cell is hovered | | onDateLeave | () => void | — | Called when the mouse leaves the calendar | | onKeyDown | (e: KeyboardEvent) => void | — | Keyboard handler on the grid element | | selectedDate | Date \| null | null | Marks the gridcell aria-selected and its interactive button aria-pressed | | cursorDate | Date \| null | null | The cell that gets tabIndex=0 (roving tabindex) | | hoveredDate | Date \| null | null | Marks the hovered cell with the is-hovered class | | cellData | (date: Date) => unknown | — | Resolve per-cell data; passed to renderCell and onDateClick | | renderCell | (day, date, state, data?) => ReactNode | — | Custom cell content renderer |

The component exports as both named Calendar and default.

Interactive Mode

Set interactive to make day cells clickable. Each cell becomes a <button class="calendar-day-btn"> — clickable, hoverable, and keyboard-accessible (Enter / Space):

<Calendar
  year={2026}
  month={9}
  interactive
  onDateClick={(date) => console.log("Selected", date)}
  onDateHover={(date) => console.log("Hovered", date)}
/>

onDateClick / onDateHover notify the caller but don't touch internal state. To select on click, either use useCalendarState's selectDateAt(date) (see below) or the ready-made InteractiveCalendar, which wires click-to-select, hover tracking, and range preview out of the box.

useCalendarState hook

For full interactivity (cursor movement, month navigation, selection), use the useCalendarState hook. It wraps the @typescript-calendar-lib/tui state machine as React state:

import { Calendar, useCalendarState } from "@typescript-calendar-lib/react";
import "@typescript-calendar-lib/react/calendar.css"; // required — component styles

function App() {
  const {
    state,        // CalendarState (tui)
    goNext,       // () => void — next month
    goPrev,       // () => void — previous month
    goToday,      // () => void — jump to today
    navigateYear, // (direction) => void — "prev" | "next" year
    goToMonth,    // (year, month) => void — jump to a specific month
    goToDate,     // (date) => void — jump to a date's month, cursor on that date
    moveCursor,   // (direction) => void — "up" | "down" | "left" | "right"
    selectDate,   // () => void — select date under cursor
    setCursorToDate, // (date) => void — move cursor to a date (same month only)
    selectDateAt, // (date) => void — move cursor to a date and select it
    selectRange,  // (from, to) => void — set a committed range (range mode)
    clearSelection,
    cursorDate,   // Date | null
    selectedDate, // Date | null
    selectedRange, // DateRange | null — committed range (range mode)
    hoveredDate,  // Date | null
    setHoveredDate, // (date | null) => void
  } = useCalendarState({
    initialYear: 2026,
    initialMonth: 9,
  });

  return (
    <>
      <button onClick={goPrev}>‹</button>
      <Calendar
        year={state.year}
        month={state.month}
        interactive
        onDateClick={selectDateAt}   // click a cell → select it
        onDateHover={setHoveredDate} // track the hovered cell
      />
      <button onClick={goNext}>›</button>
    </>
  );
}

options also accepts selectionMode: "single" | "range" (default "single") and onMonthChange: (year, month) => void, called whenever the displayed month changes.

InteractiveCalendar

InteractiveCalendar bundles useCalendarState + <Calendar> into a single self-contained component. It manages its own state and supports mouse and keyboard interaction out of the box:

| Input | Action | | :--- | :--- | | Click a cell | Selects the date (cursor moves there); fires onDateClick | | Hover a cell | Tracks the hovered date (adds is-hovered class); fires onDateHover | | Mouse leaves | Clears the hover state; fires onDateLeave | | Selection + hover | Shows a range preview between the selected date and the hovered date (is-in-range-preview class) | | Picking (range mode) | Alternates anchor → committed range → new anchor (selectionMode="range") | | Arrow keys | Move the cursor (focus follows the cursor cell) | | PageUp / PageDown | Previous / next month | | Enter / Space | Same as clicking the focused cell (selects it) |

import { InteractiveCalendar } from "@typescript-calendar-lib/react";
import "@typescript-calendar-lib/react/calendar.css"; // required — component styles

function App() {
  return (
    <InteractiveCalendar
      initialYear={2026}
      initialMonth={9}
      theme="modern"
      colorScheme="ocean"
      onMonthChange={(year, month) => console.log("Month:", year, month)}
      onDateClick={(date, data) => console.log("Selected", date, data)}
      onDateLeave={() => console.log("Mouse left the calendar")}
    />
  );
}

It accepts the useCalendarState options (initialYear, initialMonth, locale, weekStart, selectionMode, highlight, range, today, onMonthChange) plus the Calendar visual props (theme, colorScheme, size, responsive, style, cellData, renderCell) and event callbacks (onDateClick, onDateHover, onDateLeave).

Range selection

Pass selectionMode="range" to select a date range by clicking. Selection alternates between anchor → range → reset: the 1st click sets an anchor, the 2nd commits the range, and the 3rd starts a new anchor. The committed range is kept as the preview until the next pick begins.

<InteractiveCalendar
  initialYear={2026}
  initialMonth={9}
  selectionMode="range"
/>

With the raw hook, use selectRange(from, to) to set the range explicitly and read it back from the selectedRange return value:

const { state, selectRange, selectedRange } = useCalendarState({
  initialYear: 2026,
  initialMonth: 9,
  selectionMode: "range",
});

Custom Cell Rendering

Pass renderCell to replace the default day-number content of each cell. It receives the day, the full Date, the cell state, and (with cellData) the resolved data for that date:

import type { CalendarCellState } from "@typescript-calendar-lib/core";

<Calendar
  year={2026}
  month={9}
  cellData={(date) => (date.getDate() === 15 ? "holiday" : undefined)}
  renderCell={(day, date, state, data) => (
    <span>
      {day}
      {data && `(${data})`}
      {state.isToday && "★"}
      {state.isInRange && "•"}
    </span>
  )}
/>

cellData is called once per real day cell (never for empty cells) and receives the cell's full Date; returning undefined means "no data". Use it to attach anything — strings, objects, IDs — and consume it in renderCell (4th argument) or onDateClick (2nd argument):

<Calendar
  year={2026}
  month={9}
  interactive
  cellData={(date) => (date.getDate() === 15 ? "meeting" : undefined)}
  onDateClick={(date, data) => console.log(date, data)} // data: "meeting" | undefined
/>

cellData/renderCell are render-layer options — they are not part of useCalendarState (Calendar re-renders from its props, so hook-level cell data would have no visible effect).


See the interactive demo in the repository docs (docs/guide/interactive-demo.md) for a complete example.

Themes

ThemeName

type ThemeName = "default" | "modern" | "minimal" | "rounded" | "retro";

Themes are implemented as CSS classes applied to the root element:

  • calendar-theme-default
  • calendar-theme-modern
  • calendar-theme-minimal
  • calendar-theme-rounded
  • calendar-theme-retro

Pass a custom theme as an object:

const myTheme: ReactTheme = { className: "my-calendar" };

ReactTheme

interface ReactTheme {
  className: string;
}

THEMES / resolveTheme(theme?)

Predefined theme map and resolver.

Color Schemes

ColorSchemeName

type ColorSchemeName =
  | "default" | "ocean" | "forest" | "sunset"
  | "mono" | "midnight" | "blossom";

ReactColorScheme

A color scheme is a map of --cal-* CSS custom properties:

type ReactColorScheme = Record<`--cal-${string}`, string>;

Available variables:

| Variable | Purpose | | :--- | :--- | | --cal-bg | Background | | --cal-fg | Foreground | | --cal-accent | Accent (highlight) | | --cal-weekend-fg | Weekend foreground | | --cal-border | Borders | | --cal-header-bg | Header background | | --cal-highlight-bg | Highlight background | | --cal-highlight-fg | Highlight foreground | | --cal-range-bg | Range background | | --cal-range-preview-bg | Range preview background (falls back to --cal-range-bg) | | --cal-today-bg | Today background | | --cal-today-fg | Today foreground |

Pass a custom scheme directly:

const myScheme: ReactColorScheme = {
  "--cal-bg": "#ffffff",
  "--cal-fg": "#1e293b",
  "--cal-accent": "#6366f1",
  // ...all keys required
};

COLOR_SCHEMES / resolveColorScheme(scheme?)

Predefined schemes and resolver.

Sizing

CalendarSize

type CalendarSizeName = "sm" | "md" | "lg";
type CalendarCustomSize = {
  width?: number | string;
  height?: number | string;
};
type CalendarSize = CalendarSizeName | CalendarCustomSize;

Use preset sizes ("sm" | "md" | "lg"):

<Calendar year={2026} month={9} size="lg" />

Or custom pixel sizes:

<Calendar
  year={2026}
  month={9}
  size={{ width: 48, height: 40 }}
/>

Numbers are treated as pixels; strings pass through as CSS lengths ("3rem", "2.5em", etc.).

Responsive

The 7-column grid has fixed cell sizes, so on narrow phone screens it can overflow. Pass responsive to make the calendar shrink its cells, fonts, and padding below 480px (and a step tighter below 360px) and fill the viewport width:

<Calendar year={2026} month={9} responsive />

It's off by default to keep existing layouts unchanged. The calendar root gets a calendar-responsive class that drives CSS media queries — you can also add that class yourself, or use the style prop / your own CSS to tune the breakpoints further.

Styling with style

The style prop can override any CSS variable or add custom styles:

<Calendar
  year={2026}
  month={9}
  style={{ "--cal-accent": "#8b5cf6", borderRadius: "1rem" } as React.CSSProperties}
/>

Cell Classes

Each day cell gets semantic classes you can target with CSS. The table has an aria-label, weekday headers use scope="col", and week-number cells use th[scope="row"]. In interactive mode, the grid is exposed as a role="grid" table with role="gridcell" cells (WAI-ARIA APG calendar pattern): today's cell gets aria-current="date", the selected date gets aria-selected on the gridcell and aria-pressed on its button, and only the cursor cell is tabbable (tabIndex=0, roving tabindex).

| Class | When | | :--- | :--- | | is-weekend | Saturday or Sunday | | is-today | Matches today | | is-highlight | Matches highlight | | is-in-range | Inside range | | is-in-range-preview | Inside rangePreview | | is-hovered | Matches hoveredDate (interactive mode) |

Exports

import Calendar, {
  Calendar,
  COLOR_SCHEMES,
  resolveColorScheme,
  resolveTheme,
  THEMES,
  useCalendarState,
  InteractiveCalendar,
} from "@typescript-calendar-lib/react";

import type {
  CalendarCustomSize,
  CalendarProps,
  CalendarSize,
  CalendarSizeName,
  ColorSchemeName,
  InteractiveCalendarProps,
  ReactColorScheme,
  ReactTheme,
  ThemeName,
  UseCalendarStateOptions,
  UseCalendarStateReturn,
} from "@typescript-calendar-lib/react";

License

MIT