Klarhimmel UI

Date picker

Stable

A date field you can type or pick: the text takes ISO or day-first, the button opens the calendar in a dialog.

npx shadcn add @klarhimmel/date-picker @klarhimmel/input

When to use

  • Use for

    A date in a form: arrival, departure, a birthday, a due date.

  • Not for

    A time of day or a range in one control. Use two Date pickers.

  • Pairs with

    Field, Calendar, Button.

Preview

Type 2026-11-16 or 16/11/2026, or press the calendar button.

No date yet.

date-picker.tsx
import { Theme } from '@/components/klarhimmel/theme/style';
import { DatePicker } from '@/components/klarhimmel/ui/date-picker';
import { Field } from '@/components/klarhimmel/ui/input';

export default function Example() {
  return (
    <Theme theme="brand">
      <Field label="Arrival">
        {(p) => <DatePicker {...p} defaultValue="2026-11-20" today="2026-11-16" />}
      </Field>
    </Theme>
  );
}

Install

Import

import { DatePicker } from '@/components/klarhimmel/ui/date-picker';
Lands in
components/klarhimmel/ui/date-picker.tsx, plus cn() and the theme item if they are missing. Import theme.css after Tailwind.
Render in
Render it inside <Theme>, once near the root of your app. Tokens and fonts are scoped to it, so outside it the component is unstyled.
Limit
Overlays render into the Theme root with position: fixed. A transform on an ancestor re-parents fixed (a dialog centres in the wrapper) and overflow: hidden clips menus. Render <Theme> at the app root, outside any transformed wrapper.

Examples

  • Limits

    The next six weeks. Try typing 2027-01-01.

    import { DatePicker } from '@/components/klarhimmel/ui/date-picker';
    import { Field } from '@/components/klarhimmel/ui/input';
    
    export default function DatePickerLimits() {
      return (
        <div className="flex min-h-96 w-[min(24rem,calc(100cqw-3rem))] flex-col gap-4">
          <Field label="Check-in" hint="The next six weeks. Try typing 2027-01-01.">
            {(p) => <DatePicker {...p} defaultValue="2026-11-20" min="2026-11-16" max="2026-12-27" today="2026-11-16" />}
          </Field>
          <Field label="Birthday (disabled)">
            {(p) => <DatePicker {...p} defaultValue="1988-05-04" disabled />}
          </Field>
        </div>
      );
    }
  • Form

    Must be after arrival.

    The form posts both days as ISO text.

    import { useState } from 'react';
    import { Button } from '@/components/klarhimmel/ui/button';
    import { DatePicker } from '@/components/klarhimmel/ui/date-picker';
    import { Field } from '@/components/klarhimmel/ui/input';
    
    export default function DatePickerForm() {
      const [arrival, setArrival] = useState('2026-11-20');
      const [departure, setDeparture] = useState('');
      const [sent, setSent] = useState('');
      return (
        <form
          onSubmit={(e) => {
            e.preventDefault();
            setSent(new URLSearchParams(new FormData(e.currentTarget) as unknown as Record<string, string>).toString());
          }}
          className="flex min-h-[28rem] w-[min(24rem,calc(100cqw-3rem))] flex-col gap-4"
        >
          <Field label="Arrival">
            {(p) => <DatePicker {...p} name="arrival" value={arrival} onValueChange={(v) => { setArrival(v); if (departure && v && departure <= v) setDeparture(''); }} today="2026-11-16" min="2026-11-16" />}
          </Field>
          <Field label="Departure" hint="Must be after arrival.">
            {(p) => <DatePicker {...p} name="departure" value={departure} onValueChange={setDeparture} today="2026-11-16" min={arrival ? new Date(Date.parse(`${arrival}T00:00:00Z`) + 86400000).toISOString().slice(0, 10) : '2026-11-17'} />}
          </Field>
          <Button type="submit" disabled={!arrival || !departure}>Search rooms</Button>
          <p role="status" className="break-all text-kh-small text-kh-muted">{sent ? `Sent: ${sent}` : 'The form posts both days as ISO text.'}</p>
        </form>
      );
    }

API reference

PropTypeDefaultDescription
value / defaultValue / onValueChangestring / (value: string) => void-The day as 'YYYY-MM-DD'; '' means none. Typed text is read on Enter and when leaving the field.
min / max / isDisabledstring / string / (iso: string) => boolean-Days that cannot be chosen. Typing one is marked and explained in words.
today / firstDay / localestring / 'mon' | 'sun' / string- / 'mon' / 'en'Passed to the calendar.
namestring-Submits the chosen day as ISO text with a form (a hidden input).
placeholderstring'YYYY-MM-DD'Shown while the field is empty.
invalid / disabledbooleanfalseError styling; switches it off. Unreadable text is marked invalid on its own.
labels{ open; panel; previous; next; today; unreadable; outOfRange }English wordsThe button, the dialog, the calendar's buttons and the two messages. Pass them translated.
aria-label / aria-labelledbystring-Needed when no Field or <label htmlFor> names it.
classNamestring-Extra classes, merged after the component's own.

Accessibility

KeyDoes
Type2026-11-16 or day first, 16/11/2026
Enter / leaving the fieldReads the text; wrong text is marked and explained
Alt+Down on the fieldOpens the calendar
Enter / Space on the buttonOpens the calendar on the chosen day
EscapeCloses it and returns focus to the button
Arrow keys, Page Up / DownMove within the calendar (see Calendar)

The WAI-ARIA date picker dialog. Choosing a day closes it and puts the ISO text in the field. A form gets the day as YYYY-MM-DD.

Accessibility