Date Picker
A ruled field that opens a desk calendar.
Install
$ npx shadcn@latest add https://ephemera.kerrshift.com/r/date-picker.jsonOr copy the source below into components/ui/date-picker.tsx. Core components need the Paper Theme and Radix where noted.
Usage
example.tsx
import { DatePicker } from "@/components/ui/date-picker";
const [date, setDate] = useState<Date>();
<Label htmlFor="due">Due date</Label>
<DatePicker id="due" value={date} onChange={setDate} />Installs Calendar alongside it.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue / onChange | Date | — | Chosen date. |
| placeholder | string | "Pick a date" | Shown before a date is picked. |
| disabled | (date: Date) => boolean | — | Days that can't be picked. |
| locale | string | "en-US" | Formatting locale. |
Any other props are passed to the root element.
Fonts
Components read optional CSS variables and fall back to system fonts: --font-display, --font-receipt, --font-type, --font-hand and --font-stamp. This site uses Fraunces, IBM Plex Mono, Courier Prime, Caveat and Anton.
Source
components/ui/date-picker.tsx
"use client";
import * as React from "react";
import { Popover as PopoverPrimitive } from "radix-ui";
import { Calendar } from "./calendar";
function usePaperContainer() {
const anchor = React.useRef<HTMLSpanElement>(null);
const [container, setContainer] = React.useState<HTMLElement | undefined>();
React.useEffect(() => {
const el = anchor.current?.closest<HTMLElement>("[data-paper]");
setContainer(el && el !== document.documentElement ? el : undefined);
}, []);
return [anchor, container] as const;
}
export interface DatePickerProps {
value?: Date;
defaultValue?: Date;
onChange?: (date: Date) => void;
placeholder?: string;
locale?: string;
disabled?: (date: Date) => boolean;
id?: string;
className?: string;
}
/** A ruled-line field that opens a desk calendar. */
export function DatePicker({
value,
defaultValue,
onChange,
placeholder = "Pick a date",
locale = "en-US",
disabled,
id,
className = "",
}: DatePickerProps) {
const [inner, setInner] = React.useState(defaultValue);
const [open, setOpen] = React.useState(false);
const [anchor, container] = usePaperContainer();
const date = value ?? inner;
return (
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
<span ref={anchor} hidden />
<PopoverPrimitive.Trigger
id={id}
className={`flex h-10 w-full cursor-pointer items-center justify-between gap-2 border-0 border-b-[1.5px] border-(--rule) bg-transparent px-0.5 pb-1 pt-2 text-left text-[15px] outline-none transition-colors focus-visible:border-(--pen) data-[state=open]:border-(--pen) ${
date ? "text-(--ink)" : "text-(--ink-faint)"
} ${className}`}
style={{ fontFamily: "var(--font-type, 'Courier New', ui-monospace, monospace)" }}
>
{date ? new Intl.DateTimeFormat(locale, { day: "numeric", month: "short", year: "numeric" }).format(date) : placeholder}
<svg viewBox="0 0 24 24" className="size-4 shrink-0 text-(--ink-muted)" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden>
<rect x="3.5" y="5" width="17" height="15" rx="1.5" />
<path d="M3.5 10h17M8 3v4M16 3v4" strokeLinecap="round" />
</svg>
</PopoverPrimitive.Trigger>
<PopoverPrimitive.Portal container={container}>
<PopoverPrimitive.Content align="start" sideOffset={8} data-paper-motion className="z-50 outline-none data-[state=open]:animate-[paper-pop-in_.16s_ease-out]">
<Calendar
selected={date}
locale={locale}
disabled={disabled}
onSelect={(d) => {
setInner(d);
onChange?.(d);
setOpen(false);
}}
/>
</PopoverPrimitive.Content>
</PopoverPrimitive.Portal>
</PopoverPrimitive.Root>
);
}