Component
Date picker
Single-date and date-range selection from a keyboard-operable calendar.
Plain-text source, for agents and clipboard use: /components/source/date-picker.txt
When to use it
Use when a user needs to choose a date and the surrounding days help them decide — departures, arrivals, deadlines and booking ranges. The trigger matches every other Flux field, while the calendar exposes the same ISO values an ordinary form expects.
Examples
Single date
Selecting a date updates the hidden ISO value and closes the calendar. The visible value follows the familiar day / month / year order.
Choose a date
<div class="flux-field">
<span class="flux-field__label" id="departure-date-label">Departure date</span>
<div class="flux-date-picker" data-flux-date-picker data-placeholder="DD / MM / YYYY">
<button class="flux-date-picker__control" id="departure-date-trigger" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="departure-date-calendar" aria-labelledby="departure-date-label departure-date-value">
<svg class="flux-date-picker__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<rect x="1.5" y="2.5" width="13" height="12" rx="2" stroke="currentColor" stroke-width="1.25" />
<path d="M5 1V4M11 1V4M1.5 6.5H14.5" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" />
</svg>
<span class="flux-date-picker__value" id="departure-date-value">DD / MM / YYYY</span>
<svg class="flux-date-picker__chevron" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<input type="hidden" name="departureDate" value="" data-flux-date-input="start" />
<div class="flux-date-picker__panel" id="departure-date-calendar" role="dialog" aria-labelledby="departure-date-label departure-date-month" hidden>
<p class="flux-date-picker__status" aria-live="polite">Choose a date</p>
<div class="flux-date-picker__header">
<button class="flux-date-picker__nav" type="button" data-date-action="previous" aria-label="Previous month">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M10 13L5 8L10 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>
</button>
<h3 class="flux-date-picker__month" id="departure-date-month" aria-live="polite"></h3>
<button class="flux-date-picker__nav" type="button" data-date-action="next" aria-label="Next month">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M6 3L11 8L6 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>
</button>
</div>
<div class="flux-date-picker__weekdays" aria-hidden="true">
<span class="flux-date-picker__weekday">Mo</span><span class="flux-date-picker__weekday">Tu</span><span class="flux-date-picker__weekday">We</span><span class="flux-date-picker__weekday">Th</span><span class="flux-date-picker__weekday">Fr</span><span class="flux-date-picker__weekday">Sa</span><span class="flux-date-picker__weekday">Su</span>
</div>
<div class="flux-date-picker__grid" role="grid" aria-labelledby="departure-date-month"></div>
<div class="flux-date-picker__footer">
<button class="flux-date-picker__clear" type="button" data-date-action="clear" hidden>Clear</button>
</div>
</div>
</div>
</div>Date range
The first selection sets the start. The second completes and orders the range, then closes the calendar.
Choose a start date
<div class="flux-field">
<span class="flux-field__label" id="travel-dates-label">Travel dates</span>
<div class="flux-date-picker" data-flux-date-picker="range" data-placeholder="Select dates">
<button class="flux-date-picker__control" id="travel-dates-trigger" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="travel-dates-calendar" aria-labelledby="travel-dates-label travel-dates-value">
<svg class="flux-date-picker__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<rect x="1.5" y="2.5" width="13" height="12" rx="2" stroke="currentColor" stroke-width="1.25" />
<path d="M5 1V4M11 1V4M1.5 6.5H14.5" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" />
</svg>
<span class="flux-date-picker__value" id="travel-dates-value">Select dates</span>
<svg class="flux-date-picker__chevron" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<input type="hidden" name="travelStart" value="" data-flux-date-input="start" />
<input type="hidden" name="travelEnd" value="" data-flux-date-input="end" />
<div class="flux-date-picker__panel" id="travel-dates-calendar" role="dialog" aria-labelledby="travel-dates-label travel-dates-month" hidden>
<p class="flux-date-picker__status" aria-live="polite">Choose a start date</p>
<div class="flux-date-picker__header">
<button class="flux-date-picker__nav" type="button" data-date-action="previous" aria-label="Previous month">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M10 13L5 8L10 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>
</button>
<h3 class="flux-date-picker__month" id="travel-dates-month" aria-live="polite"></h3>
<button class="flux-date-picker__nav" type="button" data-date-action="next" aria-label="Next month">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M6 3L11 8L6 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>
</button>
</div>
<div class="flux-date-picker__weekdays" aria-hidden="true">
<span class="flux-date-picker__weekday">Mo</span><span class="flux-date-picker__weekday">Tu</span><span class="flux-date-picker__weekday">We</span><span class="flux-date-picker__weekday">Th</span><span class="flux-date-picker__weekday">Fr</span><span class="flux-date-picker__weekday">Sa</span><span class="flux-date-picker__weekday">Su</span>
</div>
<div class="flux-date-picker__grid" role="grid" aria-labelledby="travel-dates-month"></div>
<div class="flux-date-picker__footer">
<button class="flux-date-picker__clear" type="button" data-date-action="clear" hidden>Clear</button>
</div>
</div>
</div>
</div>Anatomy
| Part | Description |
|---|---|
| Label | Visible above the trigger and included in its accessible name. |
| Trigger | Field-height button showing the current value or format placeholder. |
| Calendar dialog | A floating overlay with month navigation, weekday headings and a date grid. |
| Date grid | Roving-tabindex grid. One date is tabbable; arrow keys move by day or week. |
| Form value | One hidden ISO date for single selection; start and end ISO dates for a range. |
API
| Name | Kind | Description |
|---|---|---|
flux-date-picker | class | Positions the field trigger and floating calendar. |
data-flux-date-picker | attribute | Initialises a single picker. Set it to "range" for start-and-end selection. |
flux-date-picker__control | class | Button trigger with standard Flux field geometry. |
flux-date-picker__panel | class | Calendar dialog surface. Hidden until the trigger opens it. |
flux-date-picker__grid | class | Generated calendar grid with roving keyboard focus. |
data-flux-date-input | attribute | Marks hidden start and optional end inputs that receive YYYY-MM-DD values. |
data-placeholder | attribute | Visible trigger text before a date has been selected. |
aria-invalid | attribute | Error state on the date-picker wrapper. Bind an adjacent message with aria-describedby. |
States
| State | Treatment |
|---|---|
| Empty | Format or selection prompt shown with text-placeholder. |
| Open | Calendar dialog visible; focus moves to the selected date, today or the first date. |
| Selected | surface-brand date with text-on-color; hidden input carries its ISO value. |
| Range | Start and end use the selected treatment; days between use surface-selected. |
| Today | border-interactive outline, independent of selection. |
| Invalid | border-error on the trigger plus a bound error message. |
Keyboard
| Keys | Action |
|---|---|
Enter, Space, Down | Opens the calendar from the trigger. |
Left, Right | Moves focus by one day. |
Up, Down | Moves focus by one week. |
Home, End | Moves to the first or last day of the current week. |
Page Up, Page Down | Moves to the same day in the previous or next month. |
Shift + Page Up, Page Down | Moves to the same day in the previous or next year. |
Enter, Space | Selects the focused date. |
Escape | Closes the calendar and returns focus to the trigger. |
Accessibility
- The trigger exposes aria-haspopup="dialog", aria-expanded and aria-controls. Its accessible name combines the visible field label and value.
- The month heading labels the grid and announces month changes through aria-live.
- Today uses aria-current="date"; selected endpoints use aria-selected. Range colour is supplementary and never the only programmatic selection state.
- Only one date has tabindex="0". Arrow, Home, End and Page keys follow the WAI-ARIA date-picker keyboard pattern.
- Hidden values use ISO YYYY-MM-DD so display formatting never changes submitted data.
- Include flux-components.js. Without it the grid cannot be generated or operated.
Tokens
The tier-3 and tier-2 tokens this component binds to. Change the token, not the component.
- --flux-field-height
- --flux-field-border
- --flux-datepicker-panel-width
- --flux-datepicker-panel-bg
- --flux-datepicker-day-size
- --flux-datepicker-selected-bg
- --flux-datepicker-selected-text
- --flux-datepicker-range-bg
- --flux-datepicker-today-border
Do and don’t
Do
- Use a single picker for one deadline or departure date, and range only when both endpoints belong to one decision.
- Keep submitted values in ISO format even when visible dates use a local format.
- Provide nearby constraints such as earliest departure or maximum stay before the user opens the calendar.
Don’t
- Do not use colour alone to identify today, selection or errors.
- Do not close a range picker after the first date; announce that the end date is now required.
- Do not omit flux-components.js or replace the visible label with the placeholder.