Detent web components
By Shane Logsdon

Forms & inputs

Date Picker

Implemented

Origin-aware popover with a spring landing

detent-date-picker

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-forms
Group
Forms & inputs

6.4 KB gzip for the class module

Variants

range · 5–25 March window
single · side=top, align=end
single · weekends disabled
single · disabled

Open a picker and choose a date to see the event detail.

Recorded states

  • disabled recorded state; no setter
  • expanded recorded state; no setter

Usage

Import

import '/components/date-picker/define.js';

// or register it yourself
import { DetentDatePicker } from '/components/date-picker/date-picker.js';

Attributes

  • align
  • default-month
  • default-value
  • disabled
  • height
  • locale
  • max-date
  • min-date
  • name
  • open
  • placeholder
  • range
  • required
  • side
  • text
  • value
  • week-starts-on

Properties

  • value
  • defaultValue
  • placeholder
  • align
  • side
  • disabled
  • range
  • height
  • text
  • locale
  • weekStartsOn
  • minDate
  • maxDate
  • defaultMonth
  • isDateDisabled
  • name
  • required
  • open
  • onChange

Slots

  • default

Events

  • detent-change — { date: Date | null } or { range: { from, to } }
  • detent-close — { reason: 'escape' | 'outside' }

Example

Runnable example: site/examples/date-picker.ts. The snippet below is generated from the catalogued attributes.

<detent-date-picker
  align="start"
  default-month
  default-value
  disabled
  height
  locale
  max-date
  min-date
  name
  open
  placeholder="Pick a date' / 'Pick a range"
  range
  required
  side="auto"
  text
  value
  week-starts-on
></detent-date-picker>

Property-only values

const element = document.querySelector('detent-date-picker');
if (element !== null) {
  // onChange: (date: Date | null) => void | (range: DateRange) => void
  element.onChange = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/date-picker.tsx sha256 cd0d869892e0…

Native files

The native module and its artifacts:

  • src/components/date-picker/date-picker.ts
  • src/components/date-picker/styles.css
  • src/components/date-picker/define.ts
  • docs/components/date-picker.md
  • site/examples/date-picker.ts
  • tests/browser/date-picker.spec.ts
  • evidence/components/date-picker.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
alignalign'start' | 'end''start'Kebab-case attribute; camelCase property.
default-month———Recorded in the catalog as an attribute.
default-valuedefaultValueDate | null | DateRangenullKebab-case attribute; camelCase property.
disableddisabledbooleanfalsePresence semantics: present means true, absent means false.
height———Recorded in the catalog as an attribute.
locale———Recorded in the catalog as an attribute.
max-date———Recorded in the catalog as an attribute.
min-date———Recorded in the catalog as an attribute.
name———Recorded in the catalog as an attribute.
open———Recorded in the catalog as an attribute.
placeholderplaceholderstring'Pick a date' / 'Pick a range'Kebab-case attribute; camelCase property.
range———Recorded in the catalog as an attribute.
required———Recorded in the catalog as an attribute.
sideside'bottom' | 'top' | 'auto''auto'Kebab-case attribute; camelCase property.
text———Recorded in the catalog as an attribute.
valuevalueDate | null | DateRangeundefinedKebab-case attribute; camelCase property.
week-starts-on———Recorded in the catalog as an attribute.

Properties

Properties
PropertyTypeDefaultNotes
valueDate | null | DateRangeundefinedProperty-only: assign from script; not accepted as an attribute.
defaultValueDate | null | DateRangenullProperty-only: assign from script; not accepted as an attribute.
placeholderstring'Pick a date' / 'Pick a range'Property-only: assign from script; not accepted as an attribute.
align'start' | 'end''start'Property-only: assign from script; not accepted as an attribute.
side'bottom' | 'top' | 'auto''auto'Property-only: assign from script; not accepted as an attribute.
disabledbooleanfalseProperty-only: assign from script; not accepted as an attribute.
range——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
height——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
text——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
locale——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
weekStartsOn——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
minDate——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
maxDate——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
defaultMonth——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
isDateDisabled——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
name——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
required——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
open——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
onChange(date: Date | null) => void | (range: DateRange) => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-changeonChange(date: Date | null) => voidComposed, bubbles; fires only for a user action.
detent-closeonClose(reason: CloseReason) => voidComposed, bubbles; fires only for a user action.

Slots

Slots
SlotNotes
defaultConsumer content; rendered through a slot, never re-created.

CSS customisation

  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • Controlled/uncontrolled React pair maps to initial-value attributes plus owned state and change events.
  • `range` is a boolean attribute (default false) that switches the inner calendar to range mode; the pinned source expresses it through props the row does not record.
  • The persistent calendar is re-anchored on open (an explicit default-month wins, else the selected month) where upstream mounted a fresh calendar per open.
  • `open` is a read-only reflected state; the Framer Motion spring is a 160ms WAAPI opacity/scale entrance, cancelled on close and disconnect and absent under reduced motion.
  • The popover is not top-layer (it matches upstream absolute positioning), so clipping by an ancestor with overflow:hidden is a recorded limitation.
  • `side="auto"` always opens downward below 640px, and otherwise flips up only when the space below is short and the trigger sits at least `height` from the top.
  • Native form participation added; the recorded `children` render function has no declarative slot equivalent.
  • The inner calendar's detent-change/detent-click/input/change are stopped at the calendar element, so a document listener sees exactly one detent-change targeted at detent-date-picker.

What it does

A date field with a popover calendar for single dates and ranges. It reuses the calendar component and emits a change when a date or range is chosen.

Upstream behaviour
  • Popover scales from the trigger edge with a spring and flips above when space is tight
  • Single date + date range pickers share the same Calendar underneath
  • Selection pill lands before the popover closes, then focus returns to the trigger
  • Esc and outside-click close; controlled or uncontrolled value
  • Supports all Calendar options: minDate, maxDate, isDateDisabled, weekStartsOn, locale
Differences from upstream
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • Controlled/uncontrolled React pair maps to initial-value attributes plus owned state and change events.
  • `range` is a boolean attribute (default false) that switches the inner calendar to range mode; the pinned source expresses it through props the row does not record.
  • The persistent calendar is re-anchored on open (an explicit default-month wins, else the selected month) where upstream mounted a fresh calendar per open.
  • `open` is a read-only reflected state; the Framer Motion spring is a 160ms WAAPI opacity/scale entrance, cancelled on close and disconnect and absent under reduced motion.
  • The popover is not top-layer (it matches upstream absolute positioning), so clipping by an ancestor with overflow:hidden is a recorded limitation.
  • `side="auto"` always opens downward below 640px, and otherwise flips up only when the space below is short and the trigger sits at least `height` from the top.
  • Native form participation added; the recorded `children` render function has no declarative slot equivalent.
  • The inner calendar's detent-change/detent-click/input/change are stopped at the calendar element, so a document listener sees exactly one detent-change targeted at detent-date-picker.
Previous: Cursor Follower Next: Density Lens

Docs

Components

Project