Quick Start
Build a working DatePicker in five minutes.
Try it live
Edit the code below — changes update the preview instantly. Kalyx is headless, so what you see is raw HTML plus the classNames you provide.
Live Editor
function LiveExample() { const [date, setDate] = React.useState(null); return ( <DatePicker value={date} onChange={setDate}> <div className="kx-live-row"> <DatePicker.Input className="kx-live-input" placeholder="YYYY-MM-DD" /> <DatePicker.Trigger className="kx-live-trigger" aria-label="Open calendar" /> </div> <DatePicker.Popover className="kx-live-popover"> <DatePicker.Calendar classNames={{ header: 'kx-live-header', title: 'kx-live-title', navButton: 'kx-live-nav', grid: 'kx-live-grid', gridCell: 'kx-live-cell', weekdayHeader: 'kx-live-weekday', day: 'live-day', daySelected: 'live-day-selected', dayToday: 'live-day-today', dayDisabled: 'kx-live-disabled', dayOutsideMonth: 'kx-live-outside', }} /> </DatePicker.Popover> <div className="kx-live-value"> Selected: <code>{date ?? 'null'}</code> </div> </DatePicker> ); }
Result
Loading...
1. Install
pnpm add @kalyx/react
2. Controlled DatePicker
import { useState } from 'react';
import { DatePicker, type ISODateString } from '@kalyx/react';
export function BirthdayPicker() {
const [date, setDate] = useState<ISODateString | null>(null);
return (
<DatePicker value={date} onChange={setDate}>
<DatePicker.Input placeholder="YYYY-MM-DD" />
<DatePicker.Trigger />
<DatePicker.Popover>
<DatePicker.Calendar />
</DatePicker.Popover>
</DatePicker>
);
}
That's the complete API. No CSS import, no provider wrapping, no setup file.
3. Style it
Every sub-component accepts classNames and forwards className/style/ref.
With Tailwind
<DatePicker value={date} onChange={setDate}>
<DatePicker.Input
className="w-48 rounded-md border px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500"
/>
<DatePicker.Popover className="mt-2 rounded-lg border bg-white p-3 shadow-lg">
<DatePicker.Calendar
classNames={{
root: 'space-y-2',
header: 'flex items-center justify-between mb-2',
title: 'font-semibold text-sm',
navButton: 'p-1 rounded hover:bg-neutral-100',
grid: 'w-full',
day: 'h-8 w-8 rounded-md text-sm hover:bg-neutral-100',
daySelected: '!bg-indigo-600 !text-white',
dayToday: 'ring-1 ring-indigo-400',
dayDisabled: 'opacity-40 pointer-events-none',
dayOutsideMonth: 'text-neutral-400',
}}
/>
</DatePicker.Popover>
</DatePicker>
See the full Tailwind recipe →.
4. Read the value
Kalyx always gives you an ISO 8601 UTC string (or null):
onChange={(iso) => {
// iso === "2026-04-15T00:00:00.000Z" | null
fetch('/api/save', { method: 'POST', body: JSON.stringify({ date: iso }) });
}}
See ISO Strings → for why.
5. Try a range
import { useState } from 'react';
import { RangePicker, type DateRange } from '@kalyx/react';
export function VacationRange() {
const [range, setRange] = useState<DateRange>({ start: null, end: null });
return (
<RangePicker value={range} onChange={setRange}>
<RangePicker.Input part="start" placeholder="Start" />
<RangePicker.Input part="end" placeholder="End" />
<RangePicker.Popover>
<RangePicker.Presets>
<RangePicker.Preset value="last7days">Last 7 days</RangePicker.Preset>
<RangePicker.Preset value="thisMonth">This month</RangePicker.Preset>
</RangePicker.Presets>
<RangePicker.Calendar />
</RangePicker.Popover>
</RangePicker>
);
}