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 drilling
function 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 components
function 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

ProviderHook Used InternallyConsumer Hook
DatePickerProvideruseDatePickeruseDatePickerContext()
DateRangePickerProvideruseDateRangePickeruseDateRangePickerContext()
DateTimePickerProvideruseDateTimePickeruseDateTimePickerContext()
DateRangeTimePickerProvideruseDateRangeTimePickeruseDateRangeTimePickerContext()
StandaloneTimePickerProvideruseStandaloneTimePickeruseStandaloneTimePickerContext()

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";
PropTypeDescription
childrenReactNodeRequired. Child components.
valueDate | nullRequired. Current date.
onChange(date: Date | null) => voidRequired. Change handler.
inlinebooleanRender inline (no popup).
sizeDatePickerSizeSize variant.
placeholderstringTrigger placeholder.
namestringForm field name.
…restBaseDatePickerOptionsAll 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";
PropTypeDescription
childrenReactNodeRequired. Child components.
value{ start: Date | null; end: Date | null }Required. Current range.
onChange(value: { start: Date | null; end: Date | null }) => voidRequired. Change handler.
inlinebooleanRender inline.
sizeDatePickerSizeSize variant.
placeholderstringTrigger placeholder.
namestringForm field name.
maxDaysnumberMax days in range.
minDaysnumberMin days in range.
presetsDateRangePreset[]Range presets.
allowSingleDateInRangebooleanAllow single-day range.
…restBaseDatePickerOptionsAll base options.

Consumer hook: useDateRangePickerContext() returns UseDateRangePickerReturn.

DateTimePickerProvider

Wraps useDateTimePicker. Provides DateTimePickerContext + PickerContext.

import type { DateTimePickerProviderProps } from "react-date-range-picker-headless";
PropTypeDescription
childrenReactNodeRequired. Child components.
valueDate | nullRequired. Current date-time.
onChange(dateTime: Date | null) => voidRequired. Change handler.
timeTimeConfigTime configuration.
inlinebooleanRender inline.
sizeDatePickerSizeSize variant.
placeholderstringTrigger placeholder.
namestringForm field name.
…restBaseDatePickerOptionsAll base options.

Consumer hook: useDateTimePickerContext() returns UseDateTimePickerReturn.

DateRangeTimePickerProvider

Wraps useDateRangeTimePicker. Provides DateRangeTimePickerContext + PickerContext.

import type { DateRangeTimePickerProviderProps } from "react-date-range-picker-headless";
PropTypeDescription
childrenReactNodeRequired. Child components.
value{ start: Date | null; end: Date | null }Required. Current range.
onChange(value: { start: Date | null; end: Date | null }) => voidRequired. Change handler.
timeTimeConfigTime configuration.
inlinebooleanRender inline.
sizeDatePickerSizeSize variant.
placeholderstringTrigger placeholder.
namestringForm field name.
maxDaysnumberMax days in range.
minDaysnumberMin days in range.
presetsDateRangePreset[]Range presets.
allowSingleDateInRangebooleanAllow single-day range.
…restBaseDatePickerOptionsAll 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)

FieldType
isOpenboolean
localeLocale
focusedDateDate | null
displayValuestring
hasValueboolean
canConfirmboolean
calendarsCalendarMonth[]
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
containerRefRefObject<HTMLDivElement | null>
popupRefRefObject<HTMLDivElement | null>
yearsnumber[]
monthsnumber[]
handleYearSelect(year: number, calendarIndex?: number) => void
handleMonthSelect(month: number, calendarIndex?: number) => void

UI Pass-Through Fields

FieldType
valueunknown
endNamestring | undefined
requiredboolean | undefined
inlineboolean | undefined
showOutsideDaysboolean | undefined
sizeDatePickerSize | undefined
placeholderstring | undefined
namestring | undefined
captionLayoutCaptionLayout | undefined

Range-Specific Fields (optional)

FieldType
handleDateHover((date: Date | null) => void) | undefined
presetsDateRangePreset[] | undefined
handlePresetClick((preset: DateRangePreset) => void) | undefined
activePresetIndexnumber | undefined

Single Time Fields (optional, DateTimePicker)

FieldType
resolvedTimeConfigRequired<TimeConfig> | undefined
tempHournumber | undefined
tempMinutenumber | undefined
tempSecondnumber | undefined
tempPeriodTimePeriod | undefined
handleHourChange((hour: number) => void) | undefined
handleMinuteChange((minute: number) => void) | undefined
handleSecondChange((second: number) => void) | undefined
handlePeriodChange((period: TimePeriod) => void) | undefined
isTimePickerOpenboolean | undefined
handleTimePickerOpen(() => void) | undefined
handleTimePickerClose(() => void) | undefined
handleTimePickerConfirm(() => void) | undefined
handleTimePickerCancel(() => void) | undefined
timePickerRefRefObject<HTMLDivElement | null> | undefined
timeDisplayValuestring | undefined

Range Time Fields (optional, DateRangeTimePicker)

FieldType
startHournumber | undefined
startMinutenumber | undefined
startSecondnumber | undefined
startPeriodTimePeriod | undefined
endHournumber | undefined
endMinutenumber | undefined
endSecondnumber | undefined
endPeriodTimePeriod | 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
isStartTimePickerOpenboolean | undefined
isEndTimePickerOpenboolean | 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
startTimePickerRefRefObject<HTMLDivElement | null> | undefined
endTimePickerRefRefObject<HTMLDivElement | null> | undefined
startTimeDisplayValuestring | undefined
endTimeDisplayValuestring | undefined

Typed vs Unified Contexts

Use CaseWhich Context
Building a shared component that works across all picker typesusePickerContext()
Building a component specific to one picker with full type safetyuseDatePickerContext(), 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>
);
}
Context-driven calendar (always open via initialOpen):
March 2026
Su
Mo
Tu
We
Th
Fr
Sa