Date adapters
Kalyx doesn't hard-wire a date library. Everything goes through a DateAdapter interface. The default implementation wraps date-fns v4, but you can ship your own.
The interface
import type { DateAdapter, ISODateString } from '@kalyx/react';
interface DateAdapter {
/** Any format → ISO 8601 UTC string. `format` is a parsing hint, not required. */
parse(value: string, format?: string): string;
/** ISO string → display string. The third argument is an IANA timezone, not a locale. */
format(iso: string, formatStr: string, timezone?: string): string;
addDays(iso: string, n: number): string;
addMonths(iso: string, n: number): string;
addYears(iso: string, n: number): string;
isBefore(a: string, b: string): boolean;
isAfter(a: string, b: string): boolean;
isSameDay(a: string, b: string, timezone?: string): boolean;
isSameMonth(a: string, b: string): boolean;
startOfDay(iso: string, timezone?: string): string;
startOfMonth(iso: string): string;
endOfMonth(iso: string): string;
startOfWeek(iso: string, weekStartsOn?: 0 | 1): string;
endOfWeek(iso: string, weekStartsOn?: 0 | 1): string;
now(): string;
today(timezone?: string): string;
isValid(value: string): boolean;
getYear(iso: string): number;
getMonth(iso: string): number;
getDate(iso: string): number;
getDay(iso: string): number;
}
Using the default adapter
DateFnsAdapter is applied automatically. You rarely need to touch it:
import { DatePicker } from '@kalyx/react';
<DatePicker value={iso} onChange={setIso}>
<DatePicker.Calendar />
</DatePicker>
If you want to be explicit:
import { DatePicker, DateFnsAdapter } from '@kalyx/react';
<DatePicker adapter={DateFnsAdapter} value={iso} onChange={setIso}>
...
</DatePicker>
Why adapters?
- Swap engines without changing component code. The next big date API is
Temporal; a lighter adapter will drop in when it lands in stable browsers. - Shrink bundles for niche use cases. Teams using Luxon or Day.js can provide their own adapter and avoid shipping date-fns.
- Test with a frozen clock. A stub adapter that returns a fixed
today()makes calendar tests deterministic.
Using a non-default adapter
Day.js and Luxon do not need a hand-written adapter any more. Both ship as published
packages, so the supported path is to install one and pass it to the /headless entry:
- npm
- Yarn
- pnpm
- Bun
npm install @kalyx/react @kalyx/adapter-dayjs dayjs
yarn add @kalyx/react @kalyx/adapter-dayjs dayjs
pnpm add @kalyx/react @kalyx/adapter-dayjs dayjs
bun add @kalyx/react @kalyx/adapter-dayjs dayjs
import { DatePicker } from '@kalyx/react/headless';
import { DayjsAdapter } from '@kalyx/adapter-dayjs';
<DatePicker adapter={DayjsAdapter} value={iso} onChange={setIso}>
<DatePicker.Input />
<DatePicker.Popover>
<DatePicker.Calendar />
</DatePicker.Popover>
</DatePicker>;
@kalyx/adapter-luxon works the same way with LuxonAdapter. The /headless entry
ships no adapter of its own, so adapter is required there; omitting it throws at
render time with a message naming the component.
Writing your own adapter
Write one only for a backend Kalyx does not publish. The contract is the DateAdapter
interface above; three details are easy to get wrong:
parsereturns an ISO string, not aDate. Itsformatargument is an optional hint.format's third argument is an IANA timezone, not a locale.isSameDay,startOfDay, andtodayall take an optionaltimezone. Ignoring it breaksdisplayTimezonefor every picker.
Keep all arithmetic in UTC. Kalyx assumes ISO strings end in Z.
Do not hand-verify the result. @kalyx/core/test-helpers exports the same conformance
suite the three official adapters run, and it is the definition of "correct" here:
import { describe, it, expect } from 'vitest';
import { runAdapterConformanceTests } from '@kalyx/core/test-helpers';
import { MyAdapter } from './my-adapter';
runAdapterConformanceTests(MyAdapter, { describe, it, expect });
Dependency note
@kalyx/core is now date-library-agnostic: it carries no date-fns
dependency of its own. The default adapter lives in @kalyx/adapter-date-fns
and is auto-installed by the main @kalyx/react entry, so installing
@kalyx/react still "just works".
If you already ship dayjs, luxon, or Temporal, you can swap the
adapter out and drop date-fns from your bundle entirely by importing from
@kalyx/react/headless. Kalyx publishes drop-in adapters for two of these
(@kalyx/adapter-dayjs and @kalyx/adapter-luxon), so you don't have to write
one yourself. See the adapters guide for the full
how-to.