Tailwind CSS
Kalyx has no built-in styles — Tailwind is a natural pairing. Every component exposes either a classNames slot map or forwards className directly.
Live preview
Tailwind is loaded via its Play CDN, scoped to the tw-enable wrapper, so every Tailwind utility you write below is rendered with the real tailwindcss at runtime.
Live Editor
function TailwindDate() { const [date, setDate] = React.useState(null); return ( <div className="tw-enable"> <DatePicker value={date} onChange={setDate}> <div className="inline-flex items-center gap-1 rounded-md border border-neutral-300 bg-white px-2.5 py-1.5 text-sm shadow-sm focus-within:ring-2 focus-within:ring-indigo-500 focus-within:border-indigo-500 dark:border-neutral-700 dark:bg-neutral-900"> <DatePicker.Input className="w-36 bg-transparent outline-none text-neutral-900 placeholder-neutral-400 dark:text-neutral-100 dark:placeholder-neutral-500" placeholder="YYYY-MM-DD" /> <DatePicker.Trigger className="inline-flex h-6 w-6 items-center justify-center rounded text-neutral-500 hover:bg-neutral-100 hover:text-neutral-700 dark:hover:bg-neutral-800 dark:hover:text-neutral-200" aria-label="Open calendar" > 📅 </DatePicker.Trigger> </div> <DatePicker.Popover className="z-50 mt-2 rounded-lg border border-neutral-200 bg-white p-4 shadow-lg ring-1 ring-black/5 dark:border-neutral-800 dark:bg-neutral-900 dark:ring-white/10"> <DatePicker.Calendar classNames={{ root: 'space-y-3', header: 'flex items-center justify-between', title: 'text-sm font-semibold text-neutral-900 dark:text-neutral-100', navButton: 'inline-flex h-7 w-7 items-center justify-center rounded-md text-neutral-500 hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-100', grid: 'w-full border-separate border-spacing-1', gridCell: 'p-0 text-center', weekdayHeader: 'pb-2 text-xs font-medium text-neutral-500 dark:text-neutral-400', day: 'inline-flex h-8 w-8 items-center justify-center rounded-full text-sm text-neutral-700 transition hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800', daySelected: '!bg-indigo-600 !text-white hover:!bg-indigo-700 !font-semibold', dayToday: '!font-semibold !text-indigo-600 dark:!text-indigo-400', dayDisabled: 'opacity-30 pointer-events-none', dayOutsideMonth: 'text-neutral-300 dark:text-neutral-600', }} /> </DatePicker.Popover> </DatePicker> </div> ); }
Result
Loading...
Full-featured DatePicker
import { DatePicker } from '@kalyx/react';
<DatePicker value={iso} onChange={setIso}>
<div className="flex items-center gap-1 rounded-md border border-neutral-300 bg-white px-2 py-1 focus-within:ring-2 focus-within:ring-indigo-500 dark:border-neutral-700 dark:bg-neutral-900">
<DatePicker.Input
className="w-40 bg-transparent outline-none text-sm text-neutral-900 dark:text-neutral-100"
placeholder="YYYY-MM-DD"
/>
<DatePicker.Trigger
className="rounded p-1 text-neutral-500 hover:bg-neutral-100 dark:hover:bg-neutral-800"
/>
</div>
<DatePicker.Popover
className="z-50 mt-2 rounded-xl border border-neutral-200 bg-white p-3 shadow-xl dark:border-neutral-800 dark:bg-neutral-900">
<DatePicker.Calendar
classNames={{
root: 'space-y-2',
header: 'flex items-center justify-between mb-1',
title: 'text-sm font-semibold',
navButton: 'rounded p-1 text-neutral-500 hover:bg-neutral-100 dark:hover:bg-neutral-800',
grid: 'w-full border-separate border-spacing-0.5',
gridRow: '',
gridCell: '',
weekdayHeader: 'text-xs font-medium text-neutral-500',
day: 'h-8 w-8 rounded-md text-sm text-neutral-800 hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800',
daySelected: '!bg-indigo-600 !text-white hover:!bg-indigo-700',
dayToday: 'ring-1 ring-indigo-400',
dayDisabled: 'opacity-40 pointer-events-none',
dayOutsideMonth: 'text-neutral-400 dark:text-neutral-600',
}}
/>
</DatePicker.Popover>
</DatePicker>
RangePicker with presets
Live Editor
function TailwindRange() { const [range, setRange] = React.useState({ start: null, end: null }); return ( <div className="tw-enable"> <RangePicker value={range} onChange={setRange}> <div className="inline-flex items-center gap-2 rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm shadow-sm focus-within:ring-2 focus-within:ring-indigo-500 focus-within:border-indigo-500 dark:border-neutral-700 dark:bg-neutral-900"> <RangePicker.Input part="start" className="w-28 bg-transparent outline-none text-neutral-900 placeholder-neutral-400 dark:text-neutral-100 dark:placeholder-neutral-500" placeholder="Start" /> <span className="text-neutral-400 dark:text-neutral-500">→</span> <RangePicker.Input part="end" className="w-28 bg-transparent outline-none text-neutral-900 placeholder-neutral-400 dark:text-neutral-100 dark:placeholder-neutral-500" placeholder="End" /> </div> <RangePicker.Popover className="z-50 mt-2 flex gap-4 rounded-lg border border-neutral-200 bg-white p-4 shadow-lg ring-1 ring-black/5 dark:border-neutral-800 dark:bg-neutral-900 dark:ring-white/10"> <RangePicker.Presets classNames={{ root: 'flex flex-col gap-0.5 border-r border-neutral-200 pr-4 text-sm min-w-[7.5rem] dark:border-neutral-800', preset: 'rounded-md px-2.5 py-1.5 text-left text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800', presetActive: '!bg-indigo-50 !text-indigo-700 !font-medium dark:!bg-indigo-950/60 dark:!text-indigo-300', }} > <RangePicker.Preset value="today">Today</RangePicker.Preset> <RangePicker.Preset value="last7days">Last 7 days</RangePicker.Preset> <RangePicker.Preset value="last30days">Last 30 days</RangePicker.Preset> <RangePicker.Preset value="thisMonth">This month</RangePicker.Preset> <RangePicker.Preset value="lastMonth">Last month</RangePicker.Preset> </RangePicker.Presets> <RangePicker.Calendar classNames={{ root: 'space-y-3', header: 'flex items-center justify-between', title: 'text-sm font-semibold text-neutral-900 dark:text-neutral-100', navButton: 'inline-flex h-7 w-7 items-center justify-center rounded-md text-neutral-500 hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-100', grid: 'w-full', gridCell: 'p-0 text-center', weekdayHeader: 'pb-2 text-xs font-medium text-neutral-500 dark:text-neutral-400', day: 'inline-flex h-8 w-8 items-center justify-center text-sm text-neutral-700 transition hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800', dayRangeStart: '!bg-indigo-600 !text-white !rounded-l-full hover:!bg-indigo-700', dayRangeEnd: '!bg-indigo-600 !text-white !rounded-r-full hover:!bg-indigo-700', dayInRange: '!bg-indigo-50 !text-indigo-900 dark:!bg-indigo-950/60 dark:!text-indigo-200', dayToday: '!font-semibold !text-indigo-600 dark:!text-indigo-400', dayDisabled: 'opacity-30 pointer-events-none', dayOutsideMonth: 'text-neutral-300 dark:text-neutral-600', }} /> </RangePicker.Popover> </RangePicker> </div> ); }
Result
Loading...
<RangePicker value={range} onChange={setRange}>
<div className="flex items-center gap-2">
<RangePicker.Input
part="start"
className="w-32 rounded-md border px-3 py-1.5 text-sm"
/>
<span className="text-neutral-400">→</span>
<RangePicker.Input
part="end"
className="w-32 rounded-md border px-3 py-1.5 text-sm"
/>
</div>
<RangePicker.Popover className="flex gap-3 rounded-xl border bg-white p-3 shadow-xl">
<RangePicker.Presets
classNames={{
root: 'flex flex-col gap-0.5 border-r pr-3 text-sm',
preset: 'rounded px-2 py-1 text-left hover:bg-neutral-100',
presetActive: '!bg-indigo-50 !text-indigo-700 font-medium',
}}>
<RangePicker.Preset value="today">Today</RangePicker.Preset>
<RangePicker.Preset value="last7days">Last 7 days</RangePicker.Preset>
<RangePicker.Preset value="last30days">Last 30 days</RangePicker.Preset>
<RangePicker.Preset value="thisMonth">This month</RangePicker.Preset>
<RangePicker.Preset value="lastMonth">Last month</RangePicker.Preset>
</RangePicker.Presets>
<RangePicker.Calendar
classNames={{
day: 'h-8 w-8 rounded-md text-sm hover:bg-neutral-100',
daySelected: '!bg-indigo-600 !text-white',
dayInRange: 'bg-indigo-50 text-indigo-900',
dayToday: 'ring-1 ring-indigo-400',
dayDisabled: 'opacity-40 pointer-events-none',
dayOutsideMonth: 'text-neutral-400',
}}
/>
</RangePicker.Popover>
</RangePicker>
TimePicker (12h)
Live Editor
function TailwindTime() { const [time, setTime] = React.useState(null); return ( <div className="tw-enable"> <TimePicker value={time} onChange={setTime} format="12h" step={15}> <TimePicker.Input className="w-24 rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus:ring-2 focus:ring-indigo-500 dark:border-neutral-700 dark:bg-neutral-900" /> <div className="mt-2 flex gap-2 rounded-md border border-neutral-200 dark:border-neutral-800 p-2"> <TimePicker.HourList classNames={{ root: 'h-40 overflow-y-auto text-sm list-none m-0 p-1', option: 'cursor-pointer rounded px-3 py-1 text-center hover:bg-neutral-100 dark:hover:bg-neutral-800 list-none', optionSelected: '!bg-indigo-600 !text-white', }} /> <TimePicker.MinuteList classNames={{ root: 'h-40 overflow-y-auto text-sm list-none m-0 p-1', option: 'cursor-pointer rounded px-3 py-1 text-center hover:bg-neutral-100 dark:hover:bg-neutral-800 list-none', optionSelected: '!bg-indigo-600 !text-white', }} /> <TimePicker.AmPmToggle classNames={{ root: 'flex flex-col gap-1', button: 'rounded border border-neutral-300 dark:border-neutral-700 px-2 py-1 text-xs', buttonSelected: '!bg-indigo-600 !text-white !border-indigo-600', }} /> </div> </TimePicker> </div> ); }
Result
Loading...
<TimePicker value={time} onChange={setTime} format="12h" step={15}>
<TimePicker.Input className="w-24 rounded-md border px-3 py-1.5 text-sm" />
<div className="mt-2 flex gap-2 rounded-md border p-2">
<TimePicker.HourList
classNames={{
root: 'h-32 overflow-y-auto text-sm',
option: 'cursor-pointer rounded px-3 py-1 hover:bg-neutral-100',
optionSelected: '!bg-indigo-600 !text-white',
}}
/>
<TimePicker.MinuteList
classNames={{
root: 'h-32 overflow-y-auto text-sm',
option: 'cursor-pointer rounded px-3 py-1 hover:bg-neutral-100',
optionSelected: '!bg-indigo-600 !text-white',
}}
/>
<TimePicker.AmPmToggle
classNames={{
root: 'flex flex-col gap-1',
button: 'rounded border px-2 py-1 text-xs',
buttonSelected: '!bg-indigo-600 !text-white !border-indigo-600',
}}
/>
</div>
</TimePicker>
Design-token tips
- Use
!modifiers sparingly — only where Kalyx applies defaults that compete (e.g.,daySelected). - Prefer semantic tokens (
bg-primary,text-on-primary) over raw colors so both themes follow automatically. - Kalyx passes ARIA attributes on every slot — target them directly instead of adding extra classes:
[aria-selected='true'] { @apply bg-indigo-600 text-white; }
[aria-disabled='true'] { @apply pointer-events-none opacity-40; }