Picker
A generic input + popover container component for building pickers such as a date picker, powered by native popover and CSS anchor positioning
Installation
Usage
Basic Usage
Click the input to toggle the popover panel. The open state and the input value each support controlled (open / value) and uncontrolled (defaultOpen / defaultValue) modes.
"use client";import { useState } from "react";import { ChevronDown } from "lucide-react";import { Picker } from "@/ui";export default function Demo() { const [value, setValue] = useState(""); return ( <Picker value={value} onValueChange={setValue} placeholder="Click to open" trailing={<ChevronDown />} > <div className="w-64 p-3 text-sm text-muted-foreground"> Any content can be rendered inside the popover panel. </div> </Picker> );}DatePicker Assembly
Picker is a building block, not a finished widget. Compose it with Calendar to assemble a date picker directly in your page — or use the shipped DatePicker component, which is this exact assembly. Selecting a date updates the input value and closes the panel via onOpenChange.
"use client";import { useState } from "react";import { Calendar as CalendarIcon } from "lucide-react";import { Calendar, Picker } from "@/ui";export default function Demo() { const [open, setOpen] = useState(false); const [text, setText] = useState(""); const [visibleMonth, setVisibleMonth] = useState(() => Temporal.Now.plainDateISO()); const [selectedDate, setSelectedDate] = useState<Temporal.PlainDate | null>(null); const handleTextChange = (text: string) => { setText(text); if (!/^\d{4}-\d{2}-\d{2}$/.test(text)) return; const date = Temporal.PlainDate.from(text); setSelectedDate(date); setVisibleMonth(date); }; return ( <Picker open={open} onOpenChange={setOpen} value={text} onValueChange={handleTextChange} placeholder="Select or type a date" trailing={<CalendarIcon />} onKeyDown={(e) => { if (e.key === "Enter" && /^\d{4}-\d{2}-\d{2}$/.test(text)) { e.preventDefault(); const date = Temporal.PlainDate.from(text); setSelectedDate(date); setVisibleMonth(date); setOpen(false); } }} > <Calendar visibleMonth={visibleMonth} onVisibleMonthChange={setVisibleMonth} value={selectedDate} onChange={(date) => { setSelectedDate(date); setVisibleMonth(date); setText(date.toString()); setOpen(false); }} firstDayOfWeek={1} /> </Picker> );}The same assembly model applies to other pickers: combine Picker with any panel content (a list, a color grid, a tree) inside the children slot.
API Reference
High-level Components
Picker
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Controlled input value |
defaultValue | string | "" | Default input value (uncontrolled) |
onValueChange | (value: string) => void | - | Fired when the input value changes |
open | boolean | - | Controlled popover open state |
defaultOpen | boolean | false | Initial open state (uncontrolled) |
onOpenChange | (open: boolean) => void | - | Fired when the popover should open or close (click, outside press, Escape) |
trailing | React.ReactNode | - | Icon rendered at the input's trailing edge |
children | React.ReactNode | - | Popover panel content |
panelRef | React.Ref<HTMLDivElement> | - | Receives the panel element, for usePanelFocus |
className | ClassNameValue | - | Custom classes, applied to the root wrapper |
style | React.CSSProperties | - | Inline styles, applied to the root wrapper |
classNames | PickerClassNames | - | Per-part class name overrides: input, trailing, popover |
styles | PickerStyles | - | Per-part style overrides: input, trailing, popover |
...props | React.ComponentProps<"input"> | - | Remaining native input props, such as placeholder and disabled |
Behavior notes:
- The popover is a native
popover="manual"element positioned with CSS anchor positioning; the panel is anchored to the input, aligned to the bottom-start edge, withmin-width: anchor-size(width)and aflip-blockfallback. - Clicking the input toggles the panel. Pressing
Escapeor pressing outside both the input and the panel closes it. - Keyboard:
ArrowDownopens the panel when it is closed, andEscapecloses it — built in, no wiring needed. - Consumer
onKeyDown/onClickhandlers are composed with the internal behavior, not overridden; the internal open and Escape-close are skipped when the consumer callspreventDefault. - In controlled mode the parent must respond to
onValueChangeby updatingvalue, otherwise typing in the input is blocked. - The popover positioning depends on CSS anchor positioning support — make sure the target environment supports it.
Picker panels come in two keyboard flavors, wired with hooks — pick the one matching the panel content:
- Virtual focus (highlighted lists): use
useCombobox. The input keeps focus while the panel is open;ArrowDown/ArrowUpmove the highlight andEnterselects. - Real focus (grids, forms, any panel of real focusable controls): use
usePanelFocuswith thepanelRefprop.ArrowDown/ArrowUpmove focus into the panel's first / last focusable element while the panel is open.
When the panel closes, focus returns to the input if it would otherwise be lost (e.g. Escape while the focus is inside the panel).
Composable Components
PickerRoot
Wrapper around the input; carries the anchor name consumed by the panel. Extends all native div props.
| Prop | Type | Default | Description |
|---|---|---|---|
className | ClassNameValue | - | Custom class names |
...props | React.ComponentProps<"div"> | - | Native div props |
PickerInput
The trigger input. Extends all native input props (except className).
| Prop | Type | Default | Description |
|---|---|---|---|
className | ClassNameValue | - | Custom class names |
...props | React.ComponentProps<"input"> | - | Native input props |
PickerContent
The popover panel. A native popover="manual" element; call showPopover() / hidePopover() imperatively, and position it manually via style={{ positionAnchor: ... }} (the anchor name set on the corresponding PickerRoot); it has no positionAnchor prop.
| Prop | Type | Default | Description |
|---|---|---|---|
className | ClassNameValue | - | Custom class names |
...props | React.ComponentProps<"div"> | - | Native div props |