Contexts
The headless package provides Context providers that wrap the hooks and expose their state via React Context. This enables the compound component pattern — split your UI into small, focused sub-components that read state from context instead of passing props down.
Why Contexts?
Without context, you’d thread every piece of state through props:
// Without context -- prop drillingfunction MyDatePicker() { const picker = useDatePicker({ value, onChange }); return ( <MyTrigger displayValue={picker.displayValue} onToggle={picker.handleToggle} /> <MyCalendar calendar={picker.calendar} getDayProps={picker.getDayProps} ... /> <MyFooter onConfirm={picker.handleConfirm} onCancel={picker.handleCancel} ... /> );}With context, sub-components consume what they need directly:
// With context -- compound componentsfunction MyDatePicker() { return ( <DatePickerProvider value={value} onChange={onChange}> <ExampleWrapper variant="headless" client:load> <MyTrigger client:load /> </ExampleWrapper> <ExampleWrapper variant="headless" client:load> <MyCalendar client:load /> </ExampleWrapper> <ExampleWrapper variant="headless" client:load> <MyFooter client:load /> </ExampleWrapper> </DatePickerProvider> );}
function MyTrigger() { const { displayValue, handleToggle } = useDatePickerContext(); return <button onClick={handleToggle}>{displayValue}</button>;}Available Providers
| Provider | Hook Used Internally | Consumer Hook |
|---|---|---|
DatePickerProvider | useDatePicker | useDatePickerContext() |
DateRangePickerProvider | useDateRangePicker | useDateRangePickerContext() |
DateTimePickerProvider | useDateTimePicker | useDateTimePickerContext() |
DateRangeTimePickerProvider | useDateRangeTimePicker | useDateRangeTimePickerContext() |
StandaloneTimePickerProvider | useStandaloneTimePicker | useStandaloneTimePickerContext() |
Each provider renders both a typed context (specific to that picker type) and the unified PickerContext (shared across all types).
import { DatePickerProvider, DateRangePickerProvider, DateTimePickerProvider, DateRangeTimePickerProvider,} from "react-date-range-picker-headless";DatePickerProvider
Wraps useDatePicker. Provides DatePickerContext + PickerContext.
import type { DatePickerProviderProps } from "react-date-range-picker-headless";| Prop | Type | Description |
|---|---|---|
children | ReactNode | Required. Child components. |
value | Date | null | Required. Current date. |
onChange | (date: Date | null) => void | Required. Change handler. |
inline | boolean | Render inline (no popup). |
size | DatePickerSize | Size variant. |
placeholder | string | Trigger placeholder. |
name | string | Form field name. |
| …rest | BaseDatePickerOptions | All base options (minDate, maxDate, locale, etc.). |
Consumer hook: useDatePickerContext() returns UseDatePickerReturn.
DateRangePickerProvider
Wraps useDateRangePicker. Provides DateRangePickerContext + PickerContext.
import type { DateRangePickerProviderProps } from "react-date-range-picker-headless";| Prop | Type | Description |
|---|---|---|
children | ReactNode | Required. Child components. |
value | { start: Date | null; end: Date | null } | Required. Current range. |
onChange | (value: { start: Date | null; end: Date | null }) => void | Required. Change handler. |
inline | boolean | Render inline. |
size | DatePickerSize | Size variant. |
placeholder | string | Trigger placeholder. |
name | string | Form field name. |
maxDays | number | Max days in range. |
minDays | number | Min days in range. |
presets | DateRangePreset[] | Range presets. |
allowSingleDateInRange | boolean | Allow single-day range. |
| …rest | BaseDatePickerOptions | All base options. |
Consumer hook: useDateRangePickerContext() returns UseDateRangePickerReturn.
DateTimePickerProvider
Wraps useDateTimePicker. Provides DateTimePickerContext + PickerContext.
import type { DateTimePickerProviderProps } from "react-date-range-picker-headless";| Prop | Type | Description |
|---|---|---|
children | ReactNode | Required. Child components. |
value | Date | null | Required. Current date-time. |
onChange | (dateTime: Date | null) => void | Required. Change handler. |
time | TimeConfig | Time configuration. |
inline | boolean | Render inline. |
size | DatePickerSize | Size variant. |
placeholder | string | Trigger placeholder. |
name | string | Form field name. |
| …rest | BaseDatePickerOptions | All base options. |
Consumer hook: useDateTimePickerContext() returns UseDateTimePickerReturn.
DateRangeTimePickerProvider
Wraps useDateRangeTimePicker. Provides DateRangeTimePickerContext + PickerContext.
import type { DateRangeTimePickerProviderProps } from "react-date-range-picker-headless";| Prop | Type | Description |
|---|---|---|
children | ReactNode | Required. Child components. |
value | { start: Date | null; end: Date | null } | Required. Current range. |
onChange | (value: { start: Date | null; end: Date | null }) => void | Required. Change handler. |
time | TimeConfig | Time configuration. |
inline | boolean | Render inline. |
size | DatePickerSize | Size variant. |
placeholder | string | Trigger placeholder. |
name | string | Form field name. |
maxDays | number | Max days in range. |
minDays | number | Min days in range. |
presets | DateRangePreset[] | Range presets. |
allowSingleDateInRange | boolean | Allow single-day range. |
| …rest | BaseDatePickerOptions | All base options. |
Consumer hook: useDateRangeTimePickerContext() returns UseDateRangeTimePickerReturn.
PickerContext (Unified)
Every provider also injects a unified PickerContext. This is a superset context that contains all fields from every picker type, with optional fields for picker-specific values.
import { usePickerContext } from "react-date-range-picker-headless";
function MyGenericHeader() { const { locale, handlePrevMonth, handleNextMonth, calendars } = usePickerContext(); return ( <div> <button onClick={handlePrevMonth}>{locale.prevMonth}</button> <span>{locale.formatMonthYear(calendars[0].month)}</span> <button onClick={handleNextMonth}>{locale.nextMonth}</button> </div> );}The unified context allows you to build shared sub-components that work across all picker types. The styled packages (styled, tailwind3, tailwind4) use this pattern internally — their Header, Grid, Footer, etc. components all consume PickerContext.
PickerContext Fields
import { PickerContext, usePickerContext } from "react-date-range-picker-headless";import type { PickerContextValue } from "react-date-range-picker-headless";function usePickerContext(): PickerContextValue;Throws if used outside a picker provider.
Core Fields (always present)
| Field | Type |
|---|---|
isOpen | boolean |
locale | Locale |
focusedDate | Date | null |
displayValue | string |
hasValue | boolean |
canConfirm | boolean |
calendars | CalendarMonth[] |
getDayProps | (date: Date, referenceMonth?: Date) => DayProps |
handleToggle | () => void |
handleOpen | () => void |
handleClose | () => void |
handleConfirm | () => void |
handleCancel | () => void |
handleClear | () => void |
handleGoToToday | () => void |
handlePrevMonth | () => void |
handleNextMonth | () => void |
handleKeyDown | (e: KeyboardEvent<HTMLElement>) => void |
handleDateClick | (date: Date) => void |
containerRef | RefObject<HTMLDivElement | null> |
popupRef | RefObject<HTMLDivElement | null> |
years | number[] |
months | number[] |
handleYearSelect | (year: number, calendarIndex?: number) => void |
handleMonthSelect | (month: number, calendarIndex?: number) => void |
UI Pass-Through Fields
| Field | Type |
|---|---|
value | unknown |
endName | string | undefined |
required | boolean | undefined |
inline | boolean | undefined |
showOutsideDays | boolean | undefined |
size | DatePickerSize | undefined |
placeholder | string | undefined |
name | string | undefined |
captionLayout | CaptionLayout | undefined |
Range-Specific Fields (optional)
| Field | Type |
|---|---|
handleDateHover | ((date: Date | null) => void) | undefined |
presets | DateRangePreset[] | undefined |
handlePresetClick | ((preset: DateRangePreset) => void) | undefined |
activePresetIndex | number | undefined |
Single Time Fields (optional, DateTimePicker)
| Field | Type |
|---|---|
resolvedTimeConfig | Required<TimeConfig> | undefined |
tempHour | number | undefined |
tempMinute | number | undefined |
tempSecond | number | undefined |
tempPeriod | TimePeriod | undefined |
handleHourChange | ((hour: number) => void) | undefined |
handleMinuteChange | ((minute: number) => void) | undefined |
handleSecondChange | ((second: number) => void) | undefined |
handlePeriodChange | ((period: TimePeriod) => void) | undefined |
isTimePickerOpen | boolean | undefined |
handleTimePickerOpen | (() => void) | undefined |
handleTimePickerClose | (() => void) | undefined |
handleTimePickerConfirm | (() => void) | undefined |
handleTimePickerCancel | (() => void) | undefined |
timePickerRef | RefObject<HTMLDivElement | null> | undefined |
timeDisplayValue | string | undefined |
Range Time Fields (optional, DateRangeTimePicker)
| Field | Type |
|---|---|
startHour | number | undefined |
startMinute | number | undefined |
startSecond | number | undefined |
startPeriod | TimePeriod | undefined |
endHour | number | undefined |
endMinute | number | undefined |
endSecond | number | undefined |
endPeriod | TimePeriod | undefined |
handleStartHourChange | ((hour: number) => void) | undefined |
handleStartMinuteChange | ((minute: number) => void) | undefined |
handleStartSecondChange | ((second: number) => void) | undefined |
handleStartPeriodChange | ((period: TimePeriod) => void) | undefined |
handleEndHourChange | ((hour: number) => void) | undefined |
handleEndMinuteChange | ((minute: number) => void) | undefined |
handleEndSecondChange | ((second: number) => void) | undefined |
handleEndPeriodChange | ((period: TimePeriod) => void) | undefined |
isStartTimePickerOpen | boolean | undefined |
isEndTimePickerOpen | boolean | undefined |
handleStartTimePickerOpen | (() => void) | undefined |
handleStartTimePickerClose | (() => void) | undefined |
handleStartTimePickerConfirm | (() => void) | undefined |
handleStartTimePickerCancel | (() => void) | undefined |
handleEndTimePickerOpen | (() => void) | undefined |
handleEndTimePickerClose | (() => void) | undefined |
handleEndTimePickerConfirm | (() => void) | undefined |
handleEndTimePickerCancel | (() => void) | undefined |
startTimePickerRef | RefObject<HTMLDivElement | null> | undefined |
endTimePickerRef | RefObject<HTMLDivElement | null> | undefined |
startTimeDisplayValue | string | undefined |
endTimeDisplayValue | string | undefined |
Typed vs Unified Contexts
| Use Case | Which Context |
|---|---|
| Building a shared component that works across all picker types | usePickerContext() |
| Building a component specific to one picker with full type safety | useDatePickerContext(), useDateRangePickerContext(), etc. |
The typed contexts return the exact hook return type, so you get full TypeScript inference. The unified context uses optional fields for picker-specific values.
Dual Context Pattern
Every provider renders two nested contexts:
<SpecificPickerContext.Provider value={hookReturn}> <PickerContext.Provider value={unifiedValue}>{children}</PickerContext.Provider></SpecificPickerContext.Provider>- Use
usePickerContext()for shared sub-components (works with any picker) - Use the typed consumer hook for picker-specific logic (full type safety)
Example: Compound Date Picker
import { useState } from "react";import { DatePickerProvider, usePickerContext, type DayProps,} from "react-date-range-picker-headless";
/** A child component that reads from PickerContext instead of receiving props. */function CalendarDisplay() { const ctx = usePickerContext();
const calendar = ctx.calendars[0];
return ( <div style={{ border: "1px solid #ccc", padding: 16, borderRadius: 8, background: "#fff" }}> {/* Header */} <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 8 }}> <button onClick={ctx.handlePrevMonth}>←</button> <span>{ctx.locale.formatMonthYear(calendar.month)}</span> <button onClick={ctx.handleNextMonth}>→</button> </div>
{/* Weekdays */} <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", textAlign: "center" }}> {ctx.locale.weekdays.map((wd) => ( <div key={wd} style={{ fontSize: 12, color: "#888", padding: 4 }}> {wd} </div> ))} </div>
{/* Days */} <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", textAlign: "center" }}> {calendar.days.map((date, i) => { if (!date) return <div key={i} />; const dp: DayProps = ctx.getDayProps(date); return ( <button key={i} onClick={() => ctx.handleDateClick(date)} disabled={dp.isDisabled} style={{ padding: 8, cursor: dp.isDisabled ? "not-allowed" : "pointer", background: dp.isSelected ? "#3b82f6" : "transparent", color: dp.isSelected ? "#fff" : dp.isToday ? "#3b82f6" : dp.isDisabled ? "#ccc" : "#333", fontWeight: dp.isToday ? "bold" : "normal", border: "none", borderRadius: 4, }} > {dp.day} </button> ); })} </div>
{/* Footer */} <div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 8 }}> <button onClick={ctx.handleCancel}>Cancel</button> <button onClick={ctx.handleConfirm} disabled={!ctx.canConfirm}> Confirm </button> </div> </div> );}
/** Main example wrapping with DatePickerProvider so children can use usePickerContext. */function ContextExample() { const [value, setValue] = useState<Date | null>(null);
return ( <div style={{ fontFamily: "system-ui" }}> <DatePickerProvider value={value} onChange={setValue} initialOpen> <div style={{ marginBottom: 8, fontSize: 14, color: "#555" }}> Context-driven calendar (always open via initialOpen): </div> <CalendarDisplay /> </DatePickerProvider> </div> );}