Skip to main content

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>
);
}

Next