@corvu/calendar

Unstyled, accessible calendar primitive for SolidJS with single, multiple, and range date selection.

Library
npm
v0.1.2
721stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
61/100Good
Development Activity56
Maintenance64
Community36
Maturity48
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
61/100Good
Architecture78
Code Quality60
Innovation62
Learning Curve45

@corvu/calendar is an unstyled, accessible calendar primitive for SolidJS, part of the corvu collection of headless UI building blocks. It renders the date-grid logic — month navigation, weekday headers, day cells, and keyboard-driven focus — without imposing any markup or styling, so teams can build a fully custom calendar or date-picker UI on top of accessible ARIA grid semantics.

The component ships a compound API (Calendar.Root, Label, Nav, Table, HeadCell, Cell, CellTrigger) built around SolidJS’s fine-grained reactivity, and supports single, multiple, and range selection modes out of the box, along with disabled-day predicates, multi-month layouts, fixed six-week grids, and RTL text direction.

What You Get

  • A compound component API (Root, Label, Nav, Table, HeadCell, Cell, CellTrigger) that mirrors the semantic structure of a calendar grid
  • Built-in single, multiple, and range selection modes with controllable or uncontrolled value and month state
  • Keyboard navigation and focus management across cells, including automatic month/year advancement when focus moves outside the visible range
  • Full ARIA grid semantics (role=grid/gridcell, aria-selected, aria-disabled) plus data-* attributes for CSS-only styling hooks
  • Multi-month rendering, fixed six-week grids, disableOutsideDays, and a disabled-day predicate for custom availability rules

Common Use Cases

  • Building a fully custom-styled date picker or booking calendar without fighting a pre-styled component library
  • Adding date-range selection to a reservation or scheduling flow with excludeDisabled range logic
  • Embedding a multi-month calendar view in a dashboard or admin panel
  • Implementing accessible keyboard-only date entry for a form

Under The Hood

Architecture The package follows a compound-component pattern built entirely on SolidJS’s fine-grained reactivity. CalendarRoot (src/Root.tsx) is the single stateful owner: it derives value/month/focusedDay via the shared @corvu/utils createControllableSignal helper (a dual controlled/uncontrolled switch keyed on whether the corresponding prop is defined), computes weekdays/months/weeks from the current month, and exposes navigation, day-selection (branching per mode: single/multiple/range), and selection/disabled predicates. State is published through two nested Solid contexts — a public CalendarContext consumed via the exported useContext, and an InternalCalendarContext carrying label-id registration and focus-tracking internals — created per-instance via a keyed-context helper so nested calendars don’t collide. Child components (Label, Nav, Table, HeadCell, Cell, CellTrigger) are dumb context consumers rendered through corvu’s shared polymorphic Dynamic/ElementOf machinery, so the rendered tag can change without touching Root. The single biggest coupling point is the mode discriminated union: several @ts-expect-error casts in Root.tsx exist specifically to bridge the single/multiple/range union with concrete signal types, so extending that union would ripple through Root and every typed consumer.

Tech Stack Strict TypeScript targeting ESNext on a SolidJS ^1.8 peer dependency, built with tsup into dual ESM/solid-JSX export conditions, linted with ESLint 10 and typescript-eslint plus eslint-plugin-solid. The only runtime dependency is the workspace-internal @corvu/utils package shared across all corvu primitives (dynamic-element, reactivity, controllable-signal, and keyed-context helpers) — zero external runtime dependencies. It lives in a pnpm/Turborepo monorepo alongside sibling primitives (accordion, dialog, drawer, popover, tooltip, resizable, otp-field, disclosure) and a separate docs site, with releases automated via release-please.

Code Quality No test files exist anywhere in the package or its shared workspace dependencies — quality is enforced through TypeScript strictness and linting rather than executable tests. tsconfig.json enables strict, strictNullChecks, and noUncheckedIndexedAccess; the ESLint config layers strict-boolean-expressions and no-unnecessary-condition on top of the typescript-eslint recommended set, a stricter-than-typical bar. Naming is consistent and namespaced per component, and JSDoc annotates nearly every public prop with its default value. A CI workflow runs lint on every push. The handful of @ts-expect-error union casts are a deliberate, documented trade-off rather than sloppiness, but the complete absence of automated tests is a real gap for a stateful, interaction-heavy primitive.

API Design The public API mirrors Radix/Kobalte-style compound components but is built natively on SolidJS primitives rather than ported: value, month, and focusedDay are each independently controllable-or-uncontrolled through the same pattern, so a consumer can leave everything uncontrolled for a minimal calendar or take over just one slice of state without extra wiring. The mode prop is a true discriminated union that changes both the value type and the available props (min/max for multiple, excludeDisabled for range) at the type level — a stronger guarantee than string-enum props with loosely-typed values. Root’s children can also be a render-prop function receiving the same props as the context, giving a full escape hatch for custom layouts. Every emitted element carries purpose-built data-* attributes (data-selected, data-today, data-range-start/end, data-in-range) for CSS-only styling. Nothing here is architecturally novel relative to sibling headless-UI libraries (Kobalte, Ark UI, Radix) — it’s a well-executed application of an established pattern to an ecosystem (SolidJS) with fewer competing options than React.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search