caliper uidocs

Date input

Free

A date field you type into, with the reading shown live and a month that carries data marks.

Installs components/ui/date-input.tsxDepends on @radix-ui/react-popover, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/date-input.json

Try “next fri”, “14 oct” or “in 2 weeks”.

Payout lands by 17:00 on 2026-10-16.

When to use #

Use it for

  • Scheduling where typing “next fri” beats clicking through months.
  • Dates where some days carry data (invoices due) or are unavailable for a reason.

Skip it when

  • People mostly browse a month; use the date picker or calendar.

Decisions #

Why it is built this way, so you know what to keep when you change your copy.

Behaviour

It shows what it read before it commits
Typed text such as “next fri” or “in 2 weeks” is read back in mono under the field, and Enter commits it, so a misread date is caught before it is saved.

Usage

Let people type
Most dates are faster to type than to pick. Keep the calendar for browsing and the field for knowing.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/date-input.json

Adds its 6 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above. It is built from DateInput and parseNaturalDate.

example.tsx
import { useState } from "react"
import { DateInput } from "@/components/ui/date-input"

export function PayoutDate() {
  const [date, setDate] = useState<string | null>("2026-10-16");
  const due: Record<string, string> = { "2026-10-14": "1 invoice due", "2026-10-18": "1 invoice due", "2026-10-21": "2 invoices due" };
  const weekend = (iso: string) => [0, 6].includes(new Date(`${iso}T12:00:00`).getDay());
  return (
    <div className="grid w-full max-w-xs gap-2 pb-80">
      <DateInput
        label="Schedule payout"
        value={date}
        onValueChange={setDate}
        today={new Date(2026, 9, 9)}
        marks={(iso) => due[iso]}
        disabledDates={weekend}
        disabledReason={(iso) => (weekend(iso) ? "banks are closed at weekends" : undefined)}
      />
      <p role="status" className="text-sm text-muted-foreground">{date ? `Payout lands by 17:00 on ${date}.` : "No date yet."}</p>
    </div>
  );
}

API reference #

DateInput

component
PropTypeDefaultDescription
label *string—
valuestring | null—
defaultValuestring | nullnull
onValueChange((value: string | null) => void)—
disabledDates((iso: string) => boolean)—
disabledReason((iso: string) => string | undefined)—
marks((iso: string) => string | undefined)—
hintstring"Try “next fri”, “14 oct” or “in 2 weeks”."
namestring—
classNamestring—
todayDate—The date relative words are read against; defaults to now.

parseNaturalDate

component

No props of its own; it accepts the native props of its root element.

Accessibility #

It is built on native elements and keeps aria-invalid, aria-describedby, aria-label, aria-hidden and aria-live in step with state. Changes that matter are announced through a live region.

KeyBehaviour
EnterActivates the focused control.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.