<bmx-date-picker>
A date typed into a field or chosen from a calendar, one date or a range, with named presets and an optional time.
29 properties · 3 events · 7 methods · 12 parts
Example
Show markup
<div class="row">
<bmx-date-picker label="Start date" description="Type it, or press Down to open the calendar. The field order follows the page's locale."></bmx-date-picker>
</div>
<div class="row">
<bmx-date-picker id="ex-date-stay" label="Stay" range="true" description="Two clicks. Clicking a date before the start begins again rather than inverting the range."></bmx-date-picker>
</div>
<div class="row">
<bmx-date-picker id="ex-date-appt" label="Appointment" with-time="true" min="2026-01-01" description="Weekends are unavailable, and the time comes from a native field."></bmx-date-picker>
</div>
<script type="module">
await customElements.whenDefined('bmx-date-picker');
// `presets` is an array and `disabled-days-of-week` is one too — both are
// properties, because neither fits in an attribute.
document.getElementById('ex-date-stay').presets = [
{ label: 'This weekend', value: ['2026-09-12', '2026-09-13'] },
{ label: 'Next week', value: ['2026-09-14', '2026-09-20'] },
];
document.getElementById('ex-date-appt').disabledDaysOfWeek = [0, 6];
</script>
WHY IT DOES NOT COMPOSE bmx-input
It should. The field is bmx-input's field, the masking is core/mask.ts,
and putting one component inside the other is the story the rest of this
library tells. It cannot, and the reason is the same one that shaped
bmx-radio-group and bmx-select: the input has to carry aria-controls
and aria-activedescendant pointing at the calendar, and an IDREF cannot
cross a shadow boundary. Nested inside bmx-input's shadow root, the input
would be referring to ids it cannot see - a combobox announced as controlling
nothing, which looks entirely correct on screen.
So the field is its own, and what is shared is shared as code: the chrome
from field-chrome.tsx, the caret arithmetic from core/mask.ts, the
surface from overlay-surface.ts. The one thing knowingly not carried across
from bmx-input is its undo-stack preservation, which is twenty lines of
execCommand for a ten-character field; when a second component needs it, it
should be extracted rather than copied.
DATES ARE NOT Dates
Not one Date object crosses into this component. src/core/calendar.ts
works on { year, month, day } because new Date('2026-03-29') is the 28th
in Honolulu, adding a day across a spring-forward lands on the day it started,
and new Date(2026, 1, 31) is the 3rd of March. Every one of those is a
booking on the wrong day that looks perfectly fine in testing.
THE LOCALE DECIDES MORE THAN THE MONTH NAMES
It decides the order of the typed fields, and that is the one that does
damage: 09/05/2026 is the 9th of May in London and the 5th of September in
New York, and both are real dates - so reading it wrong does not throw, it
books the wrong day. The order comes from Intl.DateTimeFormat.formatToParts
and the mask is built from it, so the placeholder, the mask and the parser
can never disagree.
A bare en is not neutral: Intl resolves it to US conventions, so a page
that declares lang="en" gets month/day/year whatever its author intended.
Host pages meant for the UK must say en-GB.
The first day of the week comes from Intl.Locale's week info where the
browser has it and falls back to Monday, the ISO 8601 week start, where it
does not. first-day-of-week is the escape hatch for a consumer who cannot
wait for a browser.
A KNOWN LIMIT, DELIBERATELY
In range mode the field is read-only and the calendar does the choosing.
A masked two-date field with a separator is four times the parsing for a case
people overwhelmingly use the calendar for, and the honest upgrade is two
fields rather than one cleverer one. Recorded here so it is a decision rather
than something nobody noticed.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
appearance |
appearance |
BmxDatePickerAppearance |
'outline' |
Visual treatment. |
autoFocus |
auto-focus |
boolean |
false |
Focus the field once it has rendered. |
description |
description |
string |
— | Help text below the field. |
disabled |
disabled |
boolean |
false |
Disable the field. |
disabledDates |
disabled-dates |
string[] | string |
[] |
Individual dates that cannot be chosen, as YYYY-MM-DD. Accepts a comma-separated or JSON string as well - a bank-holiday list written by a server-side template is the case this exists for. See the note on disabledDaysOfWeek for why the type says | string. |
disabledDaysOfWeek |
disabled-days-of-week |
number[] | string |
[] |
Days of the week that can never be chosen. Sunday is 0. Accepts a comma-separated or JSON string as well. The | string in the type is not decoration: without it Stencil observes this prop under its own camelCase name, which the HTML parser lowercases, so disabled-days-of-week would not exist as an attribute at all. See src/core/markup.ts. |
errorText |
error-text |
string |
— | An error supplied by the consumer. |
firstDayOfWeek |
first-day-of-week |
number |
— | Override the first day of the week. Sunday is 0. |
fullWidth |
full-width |
boolean |
false |
Stretch to the width of the container. |
hideLabel |
hide-label |
boolean |
false |
Hide the label visually while keeping it as the accessible name. |
isDateUnavailable |
property only | (iso: string) => boolean |
— | A consumer's own rule, asked after the others. |
label |
label |
string |
— | The field's label. Required unless the label slot is used. |
locale |
locale |
string |
— | The locale to write dates in. Defaults to the document's. |
max |
max |
string |
— | The latest date that may be chosen, as YYYY-MM-DD. |
messages |
property only | BmxFieldMessages |
— | Replacements for the default wording, by reason. Accepts the JSON spelling of the object as well, for templates that can only write attributes. See src/core/markup.ts. |
min |
min |
string |
— | The earliest date that may be chosen, as YYYY-MM-DD. |
name |
name |
string |
— | The field's name in the form it belongs to. |
placement |
placement |
BmxPlacement |
'bottom-start' |
Which side the calendar opens on when there is room. |
presets |
property only | BmxDatePreset[] |
[] |
Named shortcuts, shown beside the calendar. Accepts the JSON spelling of the list as well, because an attribute is the only channel some templates have. See src/core/markup.ts. |
range |
range |
boolean |
false |
Choose two dates rather than one. |
readonly |
readonly |
boolean |
false |
Make the field read-only. It still submits and is still focusable. |
required |
required |
boolean |
false |
Require a date before the form will submit. |
shape |
shape |
BmxShape |
'rounded' |
Corner treatment. |
size |
size |
BmxSize |
'md' |
Size step. |
tone |
tone |
BmxTone |
'primary' |
Semantic colour role. |
validateOn |
validate-on |
BmxValidateOn |
'blur' |
When the field is willing to reveal a problem. |
value |
value |
string | null |
null |
The chosen date as YYYY-MM-DD. Two-way: the component writes back to it. |
values |
property only | string[] |
[] |
Both ends in range mode, as YYYY-MM-DD. Two-way. Accepts a comma-separated or JSON string as well, because an attribute is the only channel some templates have. See src/core/markup.ts. |
withTime |
with-time |
boolean |
false |
Add a time field under the calendar. The value becomes YYYY-MM-DDTHH:mm. |
Events
| Event | Detail | Description |
|---|---|---|
bmxChange |
BmxDatePickerChangeDetail |
Fired when the chosen date changes. |
bmxOpenChange |
boolean |
Fired when the calendar opens or closes. |
bmxValidityChange |
BmxDatePickerValidityDetail |
Fired whenever the resolved validity changes. |
Methods
| Method | Signature | Description |
|---|---|---|
checkValidity |
checkValidity() => Promise<boolean> |
Validate now and return whether the field passed, without revealing anything. |
clear |
clear() => Promise<void> |
Empty the selection. |
closeCalendar |
closeCalendar() => Promise<void> |
Close the calendar. |
openCalendar |
openCalendar() => Promise<void> |
Open the calendar. |
removeFocus |
removeFocus() => Promise<void> |
Remove focus from the field. |
reportValidity |
reportValidity() => Promise<boolean> |
Validate, reveal any problem, and focus the field if it has one. |
setFocus |
setFocus(options?: FocusOptions) => Promise<void> |
Focus the field. |
Slots
| Slot | Description |
|---|---|
(default) |
The default slot |
description |
Rich help text, in place of the description property. |
label |
Rich label content, in place of the label property. |
CSS shadow parts
| Part | Description |
|---|---|
calendar |
The floating surface. |
control |
The text input carrying role="combobox". |
day |
One day cell. |
description |
The help text. |
error |
The error message. |
field |
The bordered box. |
grid |
The role="grid" of days. |
header |
The month name and the paging buttons. |
label |
The label element. |
preset |
One named shortcut. |
time |
The time field, with with-time. |
trigger |
The button that opens the calendar. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-date-background |
The field's background. Set by appearance. |
--bmx-date-border-color |
The field's border colour at rest. |
--bmx-date-border-width |
Border width of the field. |
--bmx-date-calendar-background |
The floating calendar's background. |
--bmx-date-calendar-shadow |
The calendar's shadow. |
--bmx-date-cell-radius |
Corner radius of a day. |
--bmx-date-cell-size |
The width and height of one day. |
--bmx-date-font-size |
The typed date's font size. |
--bmx-date-height |
The field's height. |
--bmx-date-label-font-size |
The label's font size. |
--bmx-date-padding-inline |
Horizontal padding inside the field. |
--bmx-date-radius |
Corner radius of the field and the calendar. |
--bmx-date-range-background |
The days between the two ends of a range. |
--bmx-date-selected-background |
A chosen day. Defaults to the tone's solid colour. |
--bmx-date-support-font-size |
Font size of the description and error message. |
--bmx-date-today-color |
The marker under today's date. |