Forms & inputs
Calendar
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
Pick a day to see the event detail.
Recorded states
hoverrecorded state; no setterdisabledrecorded state; no setterselectedrecorded state; no setter
Usage
Import
import '/components/calendar/define.js';
// or register it yourself
import { DetentCalendar } from '/components/calendar/calendar.js';
Attributes
default-monthdefault-valuedisabledlabellocalemax-datemin-datemodenamerequiredvalueweek-starts-on
Properties
valuedefaultValuedefaultMonthminDatemaxDateweekStartsOnlocaledisabledlabelisDateDisablednamerequiredviewMonthonChange
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.tsxsha256 907510f0676f…
Native files
The native module and its artifacts:
src/components/calendar/calendar.tssrc/components/calendar/styles.csssrc/components/calendar/define.tsdocs/components/calendar.mdsite/examples/calendar.tstests/browser/calendar.spec.tsevidence/components/calendar.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
default-month | defaultMonth | Date | undefined | Kebab-case attribute; camelCase property. |
default-value | defaultValue | Date | null | DateRange | null | Kebab-case attribute; camelCase property. |
disabled | — | — | — | Recorded in the catalog as an attribute. |
label | — | — | — | Recorded in the catalog as an attribute. |
locale | locale | string | undefined | Kebab-case attribute; camelCase property. |
max-date | maxDate | Date | undefined | Kebab-case attribute; camelCase property. |
min-date | minDate | Date | undefined | Kebab-case attribute; camelCase property. |
mode | mode | 'single' | 'range' | 'single' | Kebab-case attribute; camelCase property. |
name | — | — | — | Recorded in the catalog as an attribute. |
required | — | — | — | Recorded in the catalog as an attribute. |
value | value | Date | null | DateRange | undefined | Kebab-case attribute; camelCase property. |
week-starts-on | weekStartsOn | 0 | 1 | 0 | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
value | Date | null | DateRange | undefined | Property-only: assign from script; not accepted as an attribute. |
defaultValue | Date | null | DateRange | null | Property-only: assign from script; not accepted as an attribute. |
defaultMonth | Date | undefined | Property-only: assign from script; not accepted as an attribute. |
minDate | Date | undefined | Property-only: assign from script; not accepted as an attribute. |
maxDate | Date | undefined | Property-only: assign from script; not accepted as an attribute. |
weekStartsOn | 0 | 1 | 0 | Property-only: assign from script; not accepted as an attribute. |
locale | string | undefined | Property-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) => boolean | undefined | Property-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) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-change | onChange | (date: Date | null, range?) => void | Composed, bubbles; fires only for a user action. |
detent-click | onClick | () => void | Composed, bubbles; fires only for a user action. |
Slots
| Slot | Notes |
|---|---|
default | Consumer 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.