Maintained fork/rescue of
react-date-range. Current channel:1.2.xis the stable release line on npmlatest. It addsDatePickerInput,DateRangeInput, and named range slots (Range.label) on top of the1.1.xaccessibility/RTL/cross-month baseline. Therctag remains on1.0.0-rc.0for historical validation. See Dist-tag policy below.
This is a personal open-source initiative to rescue and modernize react-date-range,
a popular React date range picker that has been archived by its original maintainer.
Not affiliated with the original maintainers, any commercial project, or any company. This workspace exists on a personal machine for convenience; it carries no branding or association beyond what is stated here.
react-date-range has 2,600+ GitHub stars, 700+ forks, and broad real-world usage,
but the upstream repository is read-only and no longer maintained. Open issues include:
- React 19 compatibility (#661, #662)
- date-fns v3/v4 compatibility (#649, #663, #667)
- TypeScript improvements (#260, #439, #513)
- Accessibility (#373, #415/#416 — labels, roles, focus-visible states, and live-region announcements resolved)
- RTL layout support (#669)
- Cross-month passive-day range selection UX (#495)
- React 18 StrictMode scroll bugs (#577, #653)
There are community forks, but none clearly active in 2026 with a published React 19 fix. This project aims to fill that gap.
npm install @cyberlz/react-date-rangeimport { DatePickerInput, DateRangePicker } from '@cyberlz/react-date-range';
import '@cyberlz/react-date-range/styles.css';
import '@cyberlz/react-date-range/theme/default.css';Migrating from
react-date-rangeupstream? Seedocs/migration-from-upstream.md.
The fork now covers the core ARIA labels and states tracked by upstream #415/#416:
| Area | Behavior |
|---|---|
| Calendar grid | Named with ariaLabels.calendar and described with ariaLabels.calendarRoleDescription. |
| Defined ranges | Static range buttons expose aria-pressed for the active preset. |
| Input ranges | Number-of-days inputs are named by their rendered labels via aria-labelledby. |
| Date display | The start/end date inputs are grouped with role="group" and ariaLabels.dateDisplay. Per-range labels are available via Range.label — when set, the per-range wrapper renders as a named role="group" with aria-labelledby. |
| DatePickerInput | Read-only trigger input opens a named role="dialog" popover, reflects aria-expanded, closes on Escape/outside click/date selection, and returns focus to the trigger. |
| DateRangePicker | Wrapper renders as role="region" named by ariaLabels.dateRangePicker; set ariaLabels.dateRangePicker = false to opt out. |
| Calendar live region | Committed month/year navigation announced via aria-live="polite" region (customizable via ariaLabels.liveRegionMonthYear). Hover, drag movement, date selection, and scroll do not announce from Calendar itself. |
| DateRange live region | Committed range selections announced via aria-live="polite" / aria-atomic="true" after DateRange normalizes the selected range. Customizable via ariaLabels.liveRegionSelection. Hover, preview, and drag movement do not announce. |
aria-live month/year and DateRange selection announcements are available. Announcements are tied to committed state changes only, so hover, preview, and drag-move updates do not over-announce.
Assign label to each Range to name range slots for users and assistive tech:
const [ranges, setRanges] = useState([
{ startDate: new Date(), endDate: new Date('2026-07-14'), key: 'trip1', label: 'Trip 1' },
{ startDate: new Date('2026-08-01'), endDate: new Date('2026-08-07'), key: 'trip2', label: 'Trip 2' },
]);
<DateRangePicker ranges={ranges} onChange={({ trip1, trip2 }) => setRanges([trip1, trip2])} />When label is set, DateDisplay renders each range inside role="group" aria-labelledby={id} so screen readers announce the label as the group's accessible name. Labels are rendered as plain text and are XSS-safe by design.
Use DatePickerInput when the UI needs a compact input trigger instead of an always-inline Calendar.
The trigger is read-only in this first slice: users pick from the calendar popover, and manual text parsing is deferred.
import { useState } from 'react';
import { DatePickerInput } from '@cyberlz/react-date-range';
function TripDateField() {
const [date, setDate] = useState(new Date());
return (
<DatePickerInput
date={date}
onChange={setDate}
ariaLabel="Trip date"
popoverLabel="Choose trip date"
placeholder="Select a date"
calendarProps={{ months: 1 }}
/>
);
}Controlled popover state is also supported via open, defaultOpen, and onOpenChange:
<DatePickerInput
date={date}
onChange={setDate}
open={isOpen}
onOpenChange={setIsOpen}
ariaLabel="Controlled trip date"
/>Use DateRangeInput when the UI needs a compact input trigger for range selection instead of an always-inline DateRangePicker. The trigger is read-only: users pick from the calendar popover, and manual text parsing is deferred.
import { useState } from 'react';
import { DateRangeInput } from '@cyberlz/react-date-range';
function TripRangeField() {
const [ranges, setRanges] = useState([
{ startDate: new Date(), endDate: new Date('2026-07-14'), key: 'trip' },
]);
return (
<DateRangeInput
ranges={ranges}
onChange={({ trip }) => setRanges([trip])}
ariaLabel="Trip date range"
popoverLabel="Choose trip dates"
triggerPlaceholder="Select trip dates"
calendarProps={{ months: 1 }}
/>
);
}Controlled popover state is also supported via open, defaultOpen, and onOpenChange:
<DateRangeInput
ranges={ranges}
onChange={({ trip }) => setRanges([trip])}
open={isOpen}
onOpenChange={setIsOpen}
ariaLabel="Controlled trip date range"
/>Calendar, DateRange, and DateRangePicker accept an additive dir prop:
<DateRangePicker dir="rtl" direction="horizontal" />dir value |
Behavior |
|---|---|
"rtl" |
Renders dir="rtl", applies the rdrRtl class hook, mirrors navigation glyphs visually, and reverses horizontal month flow. |
"ltr" |
Renders dir="ltr" without the RTL class hook. |
| omitted | Leaves dir unset so the calendar can inherit direction from an ancestor. |
The existing direction prop still controls layout orientation ("vertical" or "horizontal"); it is separate from text direction. Consumers can override the RTL hook with classNames={{ rtl: 'my-rtl' }}.
Custom navigatorRenderer output is not wrapped or transformed by the library. If a custom renderer uses chevrons, alignment, or directional icons, it must handle RTL mirroring itself.
A modern, maintained, production-ready date range picker for React that:
- Works with React 18 and 19 without warnings or workarounds
- Is fully typed (TypeScript-first)
- Has a modern build (ESM + CJS, real tree-shaking, no side-effect imports)
- Is SSR-safe
- Preserves the existing API so current users can upgrade without rewrites
- Keeps the compatibility surface focused on the maintained
react-date-rangeexperience
| Phase | Status |
|---|---|
| Phase 0 — Audit & planning | Complete |
| Phase 1 — Compatible rescue | Complete |
| Phase 3 — Core refactor | Complete (Slices 1–21 done) |
@cyberlz/react-date-range@1.2.x is the stable release line on npm latest.
1.0/1.1/1.2 are stable, additive, and non-breaking. 1.2 adds DatePickerInput,
DateRangeInput, and named range slots (Range.label) on top of the 1.1
accessibility, RTL, and cross-month selection baseline. Future work is tracked
separately. See
docs/fork-roadmap.md for the full plan and
docs/refactor-roadmap.md for incremental refactor slices.
npm has four relevant dist-tags for this package:
latest— points to the current1.2.xstable release. Default install path:npm install @cyberlz/react-date-range.rc— points to1.0.0-rc.0. Historical release candidate for pre-release validation:npm install @cyberlz/react-date-range@rc.beta— points to0.1.0-beta.0. Legacy prerelease channel.alpha— points to0.1.0-alpha.3. Legacy prerelease channel.
For stable installs, consumers use npm install @cyberlz/react-date-range (no tag). See
docs/migration-from-upstream.md for upgrade instructions.
See docs/release-flow.md for the full policy.
Package build produces consumable dist/ output:
npm run build # tsdown (JS, multi-entry + unbundle) + sass (CSS) + types copyOutput:
| Path | Purpose |
|---|---|
dist/index.mjs |
ESM barrel (bundlers, import) |
dist/index.cjs |
CJS barrel (Node.js require) |
dist/components/<Component>/index.{mjs,cjs} |
Per-component sub-modules (preserved by multi-entry glob for real tree-shaking) |
dist/styles.css |
Compiled CSS (import separately) |
dist/index.d.ts |
TypeScript declarations |
dist/theme/default.css |
Default theme CSS |
Consumer import (ESM / TSX):
import { DateRangePicker } from '@cyberlz/react-date-range';
import '@cyberlz/react-date-range/styles.css';Consumer import (CJS):
const { DateRangePicker } = require('@cyberlz/react-date-range');
require('@cyberlz/react-date-range/styles.css');No custom Vite/esbuild loaders required — the compiled output is plain JS/CSS.
Smoke-test fixtures:
spikes/consumer-js/— JS consumer importing compiled outputspikes/consumer-tsx/— TSX consumer with type validation (tsc --noEmit)spikes/tree-shaking/— Tree-shaking analysis (Calendar-only vs DateRangePicker)
Tree-shaking works since 0.1.0-alpha.3 and remains part of the stable 1.x line. The build uses tsdown with unbundle: true and a
multi-entry glob, so each component is emitted as its own file and bundlers can drop unused
exports. Verified empirically with spikes/tree-shaking/analyze.mjs:
| Consumer import | Output size |
|---|---|
import { Calendar } |
~41 KB |
import { DateRangePicker } |
~58 KB |
| Delta | +17 KB (real tree-shaking) |
CJS consumers do not benefit equally (CJS is not tree-shakeable by design); ESM + modern bundlers (Vite, Webpack 5, esbuild, Rollup) get the full benefit.
- Breaking API changes
- Speculative visual redesign tracks such as dual skins or Tailwind theming
- Breaking feature tracks without a compatibility plan
- Local demo:
demo/is the current verified Vite consumer. During development it resolves package imports to localsrc/so unreleased fixes are exercised before publishing. - Public demo: https://sokaluis.github.io/react-date-range/ is deployed with GitHub Pages from
demo/. - Full library documentation: planned after the landing baseline, covering component props, examples, migration notes, styling, accessibility, and roadmap status.
See docs/docs-site-plan.md for the landing/docs plan and
docs/post-1.0-roadmap.md for the broader post-1.0 direction.
Upstream is MIT.
This package is published as MIT and preserves upstream attribution in NOTICE.md.
docs/research.md— Consolidated investigation: upstream status, issues, PRs, fork landscapedocs/fork-roadmap.md— Completed rescue roadmap and milestone historydocs/refactor-roadmap.md— Completed internal refactor slicesdocs/post-1.0-roadmap.md— Future direction for 1.x and 2.x/Labs (exploratory, not committed)docs/build-output.md— Build pipeline and tree-shaking detailsdocs/release-checklist.md— Canonical release checklistdocs/release-flow.md— Tag/npm/GitHub pipeline and dist-tag policydocs/npm-publishing.md— npm publishing process, costs, visibilitydocs/docs-site-plan.md— Landing page, demo, and full documentation plan