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

@mdwebb/react-calendar

v1.0.0

Published

A composable, headless event calendar for React, styled with Tailwind CSS.

Downloads

46

Readme

React Calendar renders a month-windowed list of events as either a grid or a list, with a built-in view toggle and event-type filter. It ships in three layers so you can take exactly as much structure as you need:

  • Drop-in — render one <Calendar> and get the toolbar, filter, search, navigation, and both views with sensible defaults.
  • Composable — assemble your own layout from compound components (Calendar.Root, Calendar.Toolbar, Calendar.GridView, and so on), and swap the card markup with a renderCard slot.
  • Headless — drive everything yourself with the useCalendar hook, which owns the state and derivations and renders nothing.

Every state slice (view, selected month, filters, search) is uncontrolled by default and controllable on demand: pass value + onChange to drive it from your own state (URL sync, persistence), or omit them to let the calendar manage it. Events are sorted by date, filtered by type/search/predicate, and the month window is year-aware and navigable.

Everything is written in TypeScript and tested with Vitest, React Testing Library, and Playwright.

Requirements

  • React 18 or 19
  • Tailwind CSS v3 — the components are styled with Tailwind utility classes

Installation

npm install react-calendar
# or: yarn add react-calendar
# or: pnpm add react-calendar

Peer dependencies you need installed: react and react-dom. The package also depends on @headlessui/react (filter menu), lucide-react (icons), and date-fns (date formatting).

Because the components use Tailwind utility classes, add the package to your Tailwind content globs so those classes are generated. The components ship class-based dark: variants, so enable darkMode: "class" to use them (toggle by adding the dark class to a parent, e.g. <html>):

// tailwind.config.js
export default {
  darkMode: "class",
  content: [
    "./src/**/*.{ts,tsx}",
    "./node_modules/react-calendar/dist/**/*.{js,cjs}",
  ],
  // ...
};

The calendar is responsive (cards reflow to a single column on mobile, controls stack, the month navigator scrolls horizontally) and the default cards adapt to both light and dark surfaces automatically.

Quick start

The drop-in component renders the default layout: month tabs/rail, a grid/list view toggle, an event-type filter, and the events themselves.

import { Calendar, type CalendarEvent } from "react-calendar";

export default function App() {
  const { events, loading, error } = useEvents(); // bring your own data hook

  return (
    <Calendar
      events={events}
      isLoading={loading}
      isError={error}
      onEventClick={(event) => navigate(event.url)}
    />
  );
}

Usage

Compose your own layout

Each part of the calendar is exported as a compound component under Calendar. Place them inside Calendar.Root in any arrangement, and pass renderCard to either view to replace the default card:

<Calendar.Root events={events} defaultView="grid">
  <Calendar.Toolbar>
    <Calendar.MonthTabs />
    <Calendar.ViewToggle />
    <Calendar.Filter />
  </Calendar.Toolbar>

  <Calendar.GridView renderCard={(event) => <MyCard event={event} />} />
  <Calendar.ListView />
</Calendar.Root>

Calendar.Root is the provider; every other compound component reads from it and must be rendered inside it.

Theming

Pick an overarching palette. Each palette carries a primary accent (for the toolbar/active chrome) plus a list of colours that are auto-assigned to event types, so different eventTypes read as different colours within a view. Switching palette recolours everything at once.

// Built-in palettes: "vivid" | "pastel" | "neon" | "earth" | "mono"
<Calendar events={events} theme="neon" />

// Custom palette (any field optional)
<Calendar
  events={events}
  theme={{
    accent: "#0ea5e9",
    accentText: "#0369a1",
    typeColors: ["#0ea5e9", "#22c55e", "#f97316"],
  }}
/>

Override a single event's colour with event.color (a Tailwind class). The primary accent is published as the CSS variables --rc-accent / --rc-accent-text; for a headless setup spread themeStyle(theme) onto your own container, and use buildTypeColorMap(types, theme) to mirror the per-type colours. Set showImages to render a uniform image area on grid cards (the event's image, or a themed placeholder when absent).

Go fully headless

useCalendar owns all the state and derivations and renders no markup. Use it when you want a completely custom interface:

import { useCalendar, type CalendarEvent } from "react-calendar";

function CustomCalendar({ events }: { events: CalendarEvent[] }) {
  const cal = useCalendar({ events, defaultView: "list" });

  return (
    <div>
      <div>
        {cal.availableTypes.map((type) => (
          <button key={type} onClick={() => cal.toggleType(type)}>
            {type}: {cal.filters[type] ? "shown" : "hidden"}
          </button>
        ))}
      </div>

      {cal.visibleEvents.map((event) => (
        <article key={event.id}>{event.name}</article>
      ))}
    </div>
  );
}

API reference

<Calendar> / Calendar.Root props

Both accept the same options (Calendar is the batteries-included layout; Calendar.Root is the bare provider you compose inside).

| Prop | Type | Default | Description | | -------------- | ----------------------------------- | ---------- | ------------------------------------------------------- | | events | CalendarEvent[] | required | The events to display. | | isLoading | boolean | false | Show skeleton placeholders instead of events. | | isError | boolean | false | Show the error state. | | defaultView | "grid" \| "list" | "grid" | Which view is shown first. | | monthCount | number | 3 | How many months the rolling window spans (1–12). | | now | Date | new Date() | "Today" — inject for SSR/testing determinism. | | anchorDate | Date | now | Month the window starts at. | | view / defaultView / onViewChange | controlled trio | grid | Current view (controlled or uncontrolled). | | selectedMonth / defaultSelectedMonth / onSelectedMonthChange | controlled trio | anchor month | Selected month index (0–11). | | filters / defaultFilters / onFiltersChange | controlled trio | all on | Per-type visibility map. | | search / defaultSearch / onSearchChange | controlled trio | "" | Free-text search over name + description. | | filter | (event) => boolean | — | Extra predicate (e.g. hide cancelled events). | | compareEvents| (a, b) => number | by date | Sort comparator for events within a month. | | showStatus | boolean | true | Render status badges (Live/Cancelled/Sold out/…). | | showImages | boolean | false | Render an image (or placeholder) on grid cards. | | theme | CalendarThemeName \| CalendarPalette | "vivid" | Palette: a preset name or a custom palette. | | onEventClick | (event: CalendarEvent) => void | — | Called when a card's "More Info" link is activated. | | className | string | — | Added to the root container. |

Compound components

| Component | Description | | --------------------- | ------------------------------------------------------------------------ | | Calendar.Root | Provider that owns state; wrap the others in it. | | Calendar.Toolbar | Layout slot for the controls (stacks on mobile). | | Calendar.MonthNav | Unified month navigator: prev/next arrows + scrollable month pills. | | Calendar.MonthTabs | Horizontal month switcher (pills only). | | Calendar.Nav | Previous/next controls that page the month window (arrows only). | | Calendar.Search | Text input bound to the calendar's search state. | | Calendar.ViewToggle | Grid/list switch. | | Calendar.Filter | Dropdown to toggle which event types are shown. | | Calendar.GridView | Grid view with a month rail. Accepts renderCard. | | Calendar.ListView | List view. Accepts renderCard. |

useCalendar(options)

Options match the props above (events, isLoading, isError, defaultView, monthCount). It returns:

| Field | Type | Description | | -------------------------- | ------------------------------------- | ------------------------------------------------------ | | view | "grid" \| "list" | Current view. | | setView | (view) => void | Switch the view. | | months | number[] | The rolling window of month indexes (0–11). | | selectedMonth | number | Currently selected month index. | | setSelectedMonth | (month) => void | Select a month. | | getMonthYear | (month) => number | The calendar year a windowed month belongs to. | | goToNextWindow / goToPreviousWindow | () => void | Page the window forward/back. | | search / setSearch | string / (s) => void | Current search string and its setter. | | filters | Record<string, boolean> | Map of event type to whether it is shown. | | availableTypes | string[] | Distinct event types found in events. | | toggleType | (type) => void | Toggle one type on/off. | | setAllTypes | (enabled) => void | Show or hide every type. | | allSelected | boolean | Whether every type is shown. | | noneSelected | boolean | Whether every type is hidden. | | visibleEvents | CalendarEvent[] | Events for selectedMonth after filtering. | | eventsInMonth(month) | (month) => CalendarEvent[] | Filtered events for a specific month. | | monthHasAnyEvents(month) | (month) => boolean | Whether a month has any events (ignoring filters). |

The pure helpers filterEvents, monthWindow, deriveEventTypes, the date formatters (prettyDate, prettyLongDate, ...), and cn are also exported.

CalendarEvent (schema.org-aligned)

Field names follow schema.org/Event so standards- compliant data is close to drop-in. Only id, name, eventType and startDate are required.

| Field | Type | Description | | --------------------------- | -------------------------- | ---------------------------------------------------- | | id | string | Unique identifier (required). | | name | string | Event title (required). | | eventType | string | Filter category, e.g. "football" (required). | | startDate | string | ISO date or datetime (required). | | endDate | string | ISO end, for multi-day ranges. | | description | string | Short details. | | url | string | Link used by the card action. | | image | string | Thumbnail/banner URL (schema.org image). | | eventStatus | EventStatus | scheduled | cancelled | postponed | rescheduled. | | location | EventLocation \| string | Venue (schema.org location). | | performers | string[] | Teams / competitors (schema.org performer). | | offers | EventOffer | { price, priceCurrency, availability } (schema.org).| | maximumAttendeeCapacity | number | Total capacity. | | remainingAttendeeCapacity | number | Seats left; drives the "Sold out" badge. | | color / textColor | string | Tailwind class overrides for the accent. |

Live and Completed are derived from the dates, and Full/Sold out from capacity — exported as isLive, isCompleted, isFull (plus formatPrice, locationName).

Drop in schema.org JSON-LD

fromSchemaOrg() maps one or many schema.org Events (incl. SportsEvent, MusicEvent, …), tolerating nested Place/Offer/Person objects, arrays, and URL-form enums:

import { Calendar, fromSchemaOrg } from "react-calendar";

const events = fromSchemaOrg(jsonLd); // straight from your CMS / page markup

<Calendar events={events} />;

Development

yarn dev          # run the example site
yarn test         # unit and component tests (Vitest + Testing Library)
yarn test:e2e     # end-to-end tests (Playwright)
yarn build        # build the example site
yarn build:lib    # build the publishable library (ESM + CJS + type declarations)

The example site under src/demo is a full documentation site (interactive playground, tabbed live usage examples with Shiki-highlighted code, theming, dark mode, API reference, and a live in-frame mobile preview). The library itself lives in src/lib.

License

MIT