Detent web components
By Shane Logsdon

Forms & inputs

Calendar

Implemented

Gliding selection pill & directional month slides

detent-calendar

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

7.8 KB gzip for the class module

Variants

range · 5–25 March window
single · weekends disabled
single · de-DE, Monday first
single · disabled

Pick a day to see the event detail.

Recorded states

  • hover recorded state; no setter
  • disabled recorded state; no setter
  • selected recorded state; no setter

Usage

Import

import '/components/calendar/define.js';

// or register it yourself
import { DetentCalendar } from '/components/calendar/calendar.js';

Attributes

  • default-month
  • default-value
  • disabled
  • label
  • locale
  • max-date
  • min-date
  • mode
  • name
  • required
  • value
  • week-starts-on

Properties

  • value
  • defaultValue
  • defaultMonth
  • minDate
  • maxDate
  • weekStartsOn
  • locale
  • disabled
  • label
  • isDateDisabled
  • name
  • required
  • viewMonth
  • onChange

Slots

  • default

Events

  • detent-change — { date: Date | null } or { range: { from: Date | null; to: Date | null } }
  • detent-click — {}

Example

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

<detent-calendar
  default-month
  default-value
  disabled
  label
  locale
  max-date
  min-date
  mode="single"
  name
  required
  value
  week-starts-on="0"
></detent-calendar>

Property-only values

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

Source

The pinned upstream source for this row:

  • src/components/ui/calendar.tsx sha256 907510f0676f…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
default-monthdefaultMonthDateundefinedKebab-case attribute; camelCase property.
default-valuedefaultValueDate | null | DateRangenullKebab-case attribute; camelCase property.
disabled———Recorded in the catalog as an attribute.
label———Recorded in the catalog as an attribute.
localelocalestringundefinedKebab-case attribute; camelCase property.
max-datemaxDateDateundefinedKebab-case attribute; camelCase property.
min-dateminDateDateundefinedKebab-case attribute; camelCase property.
modemode'single' | 'range''single'Kebab-case attribute; camelCase property.
name———Recorded in the catalog as an attribute.
required———Recorded in the catalog as an attribute.
valuevalueDate | null | DateRangeundefinedKebab-case attribute; camelCase property.
week-starts-onweekStartsOn0 | 10Kebab-case attribute; camelCase property.

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.
defaultMonthDateundefinedProperty-only: assign from script; not accepted as an attribute.
minDateDateundefinedProperty-only: assign from script; not accepted as an attribute.
maxDateDateundefinedProperty-only: assign from script; not accepted as an attribute.
weekStartsOn0 | 10Property-only: assign from script; not accepted as an attribute.
localestringundefinedProperty-only: assign from script; not accepted as an attribute.
disabled——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
label——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
isDateDisabled(date: Date) => booleanundefinedProperty-only: assign from script; not accepted as an attribute.
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.
viewMonth——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, range?) => voidComposed, bubbles; fires only for a user action.
detent-clickonClick() => 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.
  • `DateRange` is pinned as `{ from, to }` and re-declared so the emitted declaration stays self-contained.
  • Dates are local calendar dates with the YYYY-MM-DD (single) and START-END (range) attribute grammar; the selection is never reflected to the attribute (PC-3.3).
  • `label` names the grid (upstream's label belonged to an internal nav button) and `disabled` disables the whole calendar - recorded mapping.
  • The Framer Motion spring slide is a WAAPI animation (two keyframes, a second navigation cancels the running one) plus CSS transitions for the pill and band; reduced motion removes both.
  • Native form participation added: name, required, reset to `default-value`, formStateRestoreCallback, setValidity, external label.
  • Accessibility corrections: a labelled role=grid, out-of-month cells aria-hidden with stable hidden buttons, an aria-hidden weekday strip, and exactly one live region (upstream had two animated month labels).
  • `detent-click` is a source-interface leak (the row records onClick from the internal nav button); it is implemented, documented and tested, and is emitted once per month navigation that actually changes the month.

What it does

A month grid with a gliding selection pill, a two-pill range band with a live hover preview, and full keyboard navigation. No date library: pure Intl plus local Date helpers.

Upstream behaviour
  • Selected day is one pill that springs between dates; range mode glides start and end pills separately
  • Live hover preview band while picking a range
  • Months slide in the navigation direction; fixed height so nothing jumps between months
  • Single + range modes, min/max dates, and custom isDateDisabled
  • Full keyboard navigation: arrows, Home/End, PageUp/PageDown (Shift = year)
  • No date library — pure Intl + Date helpers
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.
  • `DateRange` is pinned as `{ from, to }` and re-declared so the emitted declaration stays self-contained.
  • Dates are local calendar dates with the YYYY-MM-DD (single) and START-END (range) attribute grammar; the selection is never reflected to the attribute (PC-3.3).
  • `label` names the grid (upstream's label belonged to an internal nav button) and `disabled` disables the whole calendar - recorded mapping.
  • The Framer Motion spring slide is a WAAPI animation (two keyframes, a second navigation cancels the running one) plus CSS transitions for the pill and band; reduced motion removes both.
  • Native form participation added: name, required, reset to `default-value`, formStateRestoreCallback, setValidity, external label.
  • Accessibility corrections: a labelled role=grid, out-of-month cells aria-hidden with stable hidden buttons, an aria-hidden weekday strip, and exactly one live region (upstream had two animated month labels).
  • `detent-click` is a source-interface leak (the row records onClick from the internal nav button); it is implemented, documented and tested, and is emitted once per month navigation that actually changes the month.
Previous: Button Next: Car Smoke Page Transition

Docs

Components

Project