You build the UI.
We handle the logic — at zero wasted re-renders.
Selection state, grid layout, range rules, disabled dates, and 100% accurate Gregorian, Hijri, and Jalali systems are all built in. Tap a day and only that cell re-renders — every other day gets 0 wasted re-renders. No more rewriting tedious calendar code; just compose your design system on top of two hooks.
yarn add react-native-headless-calendarThree providers, one mental model
Each mode ships its own selection engine — range min/max, multi-select caps, confirm/clear, and view state are handled for you. You only wire the UI.
One tap. Zero wasted re-renders.
Each day cell is an isolated reactive unit with stable props via useSyncExternalStore. Tap a date and only the cells whose selection or range state changed update — header, footer, and every idle day get zero wasted re-renders.
Stop rewriting calendar logic
Most calendar libraries make you own the hard parts — or ship opinionated UI you fight against. This one takes the tedious logic off your plate and leaves performance and pixels to you.
Zero wasted re-renders
Tap a day and only that cell updates — every other day gets 0 wasted re-renders. Header, footer, and month chrome never wake up.
You focus on UI only
No grid math, no range rules, no confirm/clear state machines. The provider owns selection logic — you wire hooks to your design system.
Tedious logic, handled
Disabled dates, min/max ranges, multi-select caps, month navigation, and calendar-system switching — all built in so you never reimplement them.
Multi-calendar systems
Gregorian, Hijri, and Jalali are 100% accurate and ship built-in. Plug in any custom system via the same adapter API.
TypeScript first
Typed providers, per-mode snapshots, cell info, and selection payloads — no `any` in the hot path.
Cross-platform
Runs on iOS, Android, and web via React Native Web — including this very page.
Headless by design
No bundled chrome. Two hooks per mode expose exactly what your cells need — nothing more, nothing less.
Your components. Our state machine.
Wrap a provider once — it owns selection, navigation, and constraints. You map hooks to Pressables and Text. No grid math in your codebase.
import { Pressable, Text, View } from 'react-native';
import {
SingleDateProvider,
selectSingleDays,
useSingleCalendarActions,
useSingleCalendarSelector,
} from 'react-native-headless-calendar';
export function PickerScreen() {
return (
<SingleDateProvider onConfirm={({ date }) => console.log(date)}>
<DayGrid />
</SingleDateProvider>
);
}
function DayGrid() {
const days = useSingleCalendarSelector(selectSingleDays);
const { selectDate, goPrevMonth, goNextMonth } = useSingleCalendarActions();
return (
<View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Pressable onPress={goPrevMonth}><Text>‹</Text></Pressable>
<Text>{days.displayedMonthLabel} {days.displayedYearLabel}</Text>
<Pressable onPress={goNextMonth}><Text>›</Text></Pressable>
</View>
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
{days.cells.map((cell) => (
<Pressable
key={cell.nativeDate.toISOString()}
onPress={() => selectDate(cell.date)}
disabled={cell.isDisabled}
>
<Text>{cell.label}</Text>
</Pressable>
))}
</View>
</View>
);
}
import { Pressable, Text, View } from 'react-native';
import {
RangeDateProvider,
selectRangeCanConfirm,
selectRangeDays,
useRangeCalendarActions,
useRangeCalendarSelector,
} from 'react-native-headless-calendar';
export function CustomCalendar() {
return (
<RangeDateProvider>
<Grid />
<Footer />
</RangeDateProvider>
);
}
function Grid() {
const days = useRangeCalendarSelector(selectRangeDays);
const { selectDate } = useRangeCalendarActions(); // stable, zero subscriptions
return (
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
{days.cells.map((cell) => (
<Pressable
key={cell.nativeDate.toISOString()}
onPress={() => selectDate(cell.date)}
>
<Text>{cell.label}</Text>
</Pressable>
))}
</View>
);
}
function Footer() {
const start = useRangeCalendarSelector((s) => s.rangeStart);
const { confirm } = useRangeCalendarActions();
const canConfirm = useRangeCalendarSelector(selectRangeCanConfirm);
return (
<View>
<Text>{start ? 'Pick checkout' : 'Pick check-in'}</Text>
<Pressable onPress={confirm} disabled={!canConfirm}>
<Text>Done</Text>
</Pressable>
</View>
);
}
import { SingleDateProvider } from 'react-native-headless-calendar';
import { gregorianSystem } from 'react-native-headless-calendar';
import { hijriSystem } from 'react-native-headless-calendar/systems/hijri';
import { jalaliSystem } from 'react-native-headless-calendar/systems/jalali';
const systems = [gregorianSystem, hijriSystem, jalaliSystem];
export function MultiSystemPicker() {
return (
<SingleDateProvider systems={systems} activeSystemId="hijri">
<MyDayGrid />
</SingleDateProvider>
);
}
Ship a fast calendar without the tedious logic.
Drop in a provider, subscribe with two hooks, and style your cells — 0 wasted re-renders on idle days, 100% focus on the UI your product needs.