Skip to main content
React Native · iOS · Android · Web

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-calendar
0
wasted re-renders on idle cells
1
cell updates per tap
2
hooks per mode
100%
accurate built-in systems
Pick your mode

Three 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.

High performance

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.

Why this calendar

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.

Two layers, one library

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.

Date picker
SingleDateProvider · your own UI
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>
);
}
Booking screen
RangeDateProvider · grid + confirm footer
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>
);
}
Multiple calendar systems
100% accurate Gregorian, Hijri, and Jalali — or your own. Switch at runtime.
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.