Date Picker
formUSWDS-derivedinteractive
A calendar date selector popup from a text input with keyboard navigation and min/max date support.
Reference: USWDS documentation ↗
Variants
Date Picker With Constraints
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|
<flex-date-picker data-min-date="2020-01-01" data-max-date="2030-12-31">
<label class="flex-label" for="appointment">Appointment date</label>
<div class="flex-date-picker__wrapper">
<input class="flex-input flex-date-picker__external-input" id="appointment" name="appointment" type="text" placeholder="mm/dd/yyyy" value=""/>
<button type="button" class="flex-date-picker__button" aria-label="Toggle calendar">
<svg class="flex-icon" aria-hidden="true" focusable="false">
<use href="/static/sprite.svg#event">
</use>
</svg>
</button>
<div class="flex-date-picker__calendar" hidden="" role="application" aria-label="Calendar">
<div class="flex-date-picker__calendar-header">
<button type="button" class="flex-date-picker__nav flex-date-picker__nav--prev" aria-label="Previous month">
<svg class="flex-icon" aria-hidden="true" focusable="false">
<use href="/static/sprite.svg#navigate_before">
</use>
</svg>
</button>
<button type="button" class="flex-date-picker__month-label" aria-label="Select month">
</button>
<button type="button" class="flex-date-picker__nav flex-date-picker__nav--next" aria-label="Next month">
<svg class="flex-icon" aria-hidden="true" focusable="false">
<use href="/static/sprite.svg#navigate_next">
</use>
</svg>
</button>
</div>
<table class="flex-date-picker__table" role="presentation">
<thead>
<tr>
<th abbr="Sunday">Su</th>
<th abbr="Monday">Mo</th>
<th abbr="Tuesday">Tu</th>
<th abbr="Wednesday">We</th>
<th abbr="Thursday">Th</th>
<th abbr="Friday">Fr</th>
<th abbr="Saturday">Sa</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</flex-date-picker>Date Picker With Default
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|
<flex-date-picker data-default-value="1990-06-15">
<label class="flex-label" for="birthday">Date of birth</label>
<div class="flex-date-picker__wrapper">
<input class="flex-input flex-date-picker__external-input" id="birthday" name="birthday" type="text" placeholder="mm/dd/yyyy" value="06/15/1990"/>
<button type="button" class="flex-date-picker__button" aria-label="Toggle calendar">
<svg class="flex-icon" aria-hidden="true" focusable="false">
<use href="/static/sprite.svg#event">
</use>
</svg>
</button>
<div class="flex-date-picker__calendar" hidden="" role="application" aria-label="Calendar">
<div class="flex-date-picker__calendar-header">
<button type="button" class="flex-date-picker__nav flex-date-picker__nav--prev" aria-label="Previous month">
<svg class="flex-icon" aria-hidden="true" focusable="false">
<use href="/static/sprite.svg#navigate_before">
</use>
</svg>
</button>
<button type="button" class="flex-date-picker__month-label" aria-label="Select month">
</button>
<button type="button" class="flex-date-picker__nav flex-date-picker__nav--next" aria-label="Next month">
<svg class="flex-icon" aria-hidden="true" focusable="false">
<use href="/static/sprite.svg#navigate_next">
</use>
</svg>
</button>
</div>
<table class="flex-date-picker__table" role="presentation">
<thead>
<tr>
<th abbr="Sunday">Su</th>
<th abbr="Monday">Mo</th>
<th abbr="Tuesday">Tu</th>
<th abbr="Wednesday">We</th>
<th abbr="Thursday">Th</th>
<th abbr="Friday">Fr</th>
<th abbr="Saturday">Sa</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</flex-date-picker>Default Date Picker
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|
<flex-date-picker>
<label class="flex-label" for="date">Date</label>
<div class="flex-date-picker__wrapper">
<input class="flex-input flex-date-picker__external-input" id="date" name="date" type="text" placeholder="mm/dd/yyyy" value=""/>
<button type="button" class="flex-date-picker__button" aria-label="Toggle calendar">
<svg class="flex-icon" aria-hidden="true" focusable="false">
<use href="/static/sprite.svg#event">
</use>
</svg>
</button>
<div class="flex-date-picker__calendar" hidden="" role="application" aria-label="Calendar">
<div class="flex-date-picker__calendar-header">
<button type="button" class="flex-date-picker__nav flex-date-picker__nav--prev" aria-label="Previous month">
<svg class="flex-icon" aria-hidden="true" focusable="false">
<use href="/static/sprite.svg#navigate_before">
</use>
</svg>
</button>
<button type="button" class="flex-date-picker__month-label" aria-label="Select month">
</button>
<button type="button" class="flex-date-picker__nav flex-date-picker__nav--next" aria-label="Next month">
<svg class="flex-icon" aria-hidden="true" focusable="false">
<use href="/static/sprite.svg#navigate_next">
</use>
</svg>
</button>
</div>
<table class="flex-date-picker__table" role="presentation">
<thead>
<tr>
<th abbr="Sunday">Su</th>
<th abbr="Monday">Mo</th>
<th abbr="Tuesday">Tu</th>
<th abbr="Wednesday">We</th>
<th abbr="Thursday">Th</th>
<th abbr="Friday">Fr</th>
<th abbr="Saturday">Sa</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</flex-date-picker>Contract
Class mapping
| USWDS | Flex | Notes |
|---|---|---|
usa-date-picker | <flex-date-picker> (custom element) | Container element |
usa-date-picker__external-input | .flex-date-picker__external-input | Text input for date entry (mm/dd/yyyy) |
usa-date-picker__button | .flex-date-picker__button | Calendar toggle button |
usa-date-picker__calendar | .flex-date-picker__calendar | Calendar popup container |
usa-date-picker__calendar__previous-month | .flex-date-picker__nav--prev | Previous month navigation button |
usa-date-picker__calendar__next-month | .flex-date-picker__nav--next | Next month navigation button |
usa-date-picker__calendar__month-selection | .flex-date-picker__month-label | Month/year label that opens month selection |
usa-date-picker__calendar__date | .flex-date-picker__day | Individual day button in calendar grid |
usa-date-picker__calendar__date--selected | .flex-date-picker__day--selected | Selected date styling |
usa-date-picker__calendar__date--today | .flex-date-picker__day--today | Today date styling |
Verified properties
font-familyfont-sizecolorbackground-colorcursorIntentional differences
background-color: ours = var(--flex-gray-5) (#f0f0f0) — calendar background, USWDS = #f0f0f0 — same color, different token
Using design token for theming support
Behavior promises
- ✓ Click toggle button opens calendar popup
- ✓ Click toggle button again closes calendar
- ✓ Calendar renders correct days for the month
- ✓ Previous/Next month buttons navigate months
- ✓ Click day selects date and closes calendar
- ✓ Selected date appears in input as mm/dd/yyyy
- ✓ ArrowLeft/Right moves focus by day
- ✓ ArrowUp/Down moves focus by week
- ✓ Home/End moves to first/last day of week
- ✓ PageUp/PageDown moves by month
- ✓ Enter selects focused date
- ✓ Escape closes calendar
- ✓ Outside click closes calendar
- ✓ Min/max date constraints disable out-of-range days
- ✓ Month label click toggles month selection view
- ✓ Today is visually highlighted
- ✓ Selected date has accent background
- ✓ Calendar opens with focus on selected or today date
- ✓ Accessibility audit passes
Source CSS
Base styles: Form Control Base
/* flex-date-picker -- USWDS Date Picker conformance
Extends: Form Control Base (base-classes.css#form-control)
Features: calendar popup, month/year navigation, keyboard navigation */
flex-date-picker {
display: block;
max-inline-size: var(--flex-control-max-width);
}
.flex-date-picker__wrapper {
position: relative;
display: flex;
align-items: stretch;
margin-block-start: var(--flex-control-margin-top);
}
/* Input — extends form control base styles */
.flex-date-picker__external-input {
font-size: var(--flex-text-uswds);
line-height: 1.3;
color: var(--flex-color-text);
background-color: var(--flex-color-surface);
border: var(--flex-control-border);
border-radius: 0;
appearance: none;
display: block;
inline-size: 100%;
block-size: var(--flex-control-height);
padding: var(--flex-control-padding);
margin-block-start: 0;
&:focus-visible {
outline: var(--flex-focus-ring);
outline-offset: var(--flex-focus-offset-inset);
}
}
/* Toggle button */
.flex-date-picker__button {
display: flex;
align-items: center;
justify-content: center;
inline-size: 3rem;
padding: 0;
background-color: var(--flex-gray-5);
border: var(--flex-control-border);
border-inline-start: none;
cursor: pointer;
flex-shrink: 0;
&:hover {
background-color: var(--flex-gray-cool-5);
}
&:focus-visible {
outline: var(--flex-focus-ring);
outline-offset: var(--flex-focus-offset-inset);
}
& .flex-icon {
inline-size: 1.5rem;
block-size: 1.5rem;
}
}
/* Calendar popup */
.flex-date-picker__calendar {
position: absolute;
inset-block-start: 100%;
inset-inline-end: 0;
z-index: 400;
inline-size: 100%;
/* stylelint-disable-next-line declaration-property-value-allowed-list -- Calendar popup intrinsic width (7 day columns). */
max-inline-size: 20rem;
background-color: var(--flex-gray-5);
border: 1px solid var(--flex-color-ink);
box-shadow: 0 4px 8px rgb(0 0 0 / 10%);
&[hidden] {
display: none;
}
}
/* Calendar header */
.flex-date-picker__calendar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.25rem;
}
/* Nav buttons (prev/next month) */
.flex-date-picker__nav {
display: flex;
align-items: center;
justify-content: center;
inline-size: 2.5rem;
block-size: 2.5rem;
padding: 0;
background-color: var(--flex-gray-5);
border: none;
cursor: pointer;
&:hover {
background-color: var(--flex-gray-cool-20);
}
&:focus-visible {
outline: var(--flex-focus-ring);
outline-offset: -4px;
}
& .flex-icon {
inline-size: 1.5rem;
block-size: 1.5rem;
}
}
/* Month/year label button */
.flex-date-picker__month-label {
flex: 1;
padding: 0.5rem;
font-size: var(--flex-text-uswds);
font-weight: 700;
text-align: center;
background-color: var(--flex-gray-5);
border: none;
cursor: pointer;
&:hover {
background-color: var(--flex-gray-cool-20);
}
&:focus-visible {
outline: var(--flex-focus-ring);
outline-offset: -4px;
}
}
/* Calendar table */
.flex-date-picker__table {
border-spacing: 0;
border-collapse: collapse;
table-layout: fixed;
text-align: center;
inline-size: 100%;
}
.flex-date-picker__table th {
font-size: 0.87rem;
font-weight: 400;
color: var(--flex-color-text-muted);
padding: 0.375rem 0;
}
.flex-date-picker__table td {
padding: 0;
}
/* Day buttons */
.flex-date-picker__day {
display: flex;
align-items: center;
justify-content: center;
inline-size: 100%;
aspect-ratio: 1;
padding: 0;
font-size: 0.93rem;
line-height: 1;
color: var(--flex-color-text);
background-color: var(--flex-gray-5);
border: none;
cursor: pointer;
&:not(:disabled):hover {
background-color: var(--flex-gray-cool-20);
}
&:not(:disabled):active {
background-color: var(--flex-gray-cool-30);
}
&:focus-visible {
outline: var(--flex-focus-ring);
outline-offset: -4px;
z-index: 1;
}
&:disabled {
cursor: not-allowed;
opacity: 0.4;
}
}
/* Outside month days (grayed out) */
.flex-date-picker__day--outside {
color: var(--flex-color-text-muted);
opacity: 0.4;
}
/* Today */
.flex-date-picker__day--today {
font-weight: 700;
box-shadow: inset 0 0 0 2px var(--flex-color-accent);
}
/* Selected day */
.flex-date-picker__day--selected {
background-color: var(--flex-blue-vivid-60);
color: var(--flex-white);
font-weight: 700;
&:not(:disabled):hover {
background-color: var(--flex-blue-vivid-70);
}
}
/* Focused day (keyboard navigation) */
.flex-date-picker__day--focused:not(.flex-date-picker__day--selected) {
background-color: var(--flex-gray-cool-10);
}
/* Month selection grid */
.flex-date-picker__month-option {
display: flex;
align-items: center;
justify-content: center;
inline-size: 100%;
padding: 0.75rem;
font-size: var(--flex-text-uswds);
color: var(--flex-color-text);
background-color: var(--flex-gray-5);
border: none;
cursor: pointer;
&:hover {
background-color: var(--flex-gray-cool-20);
}
&:focus-visible {
outline: var(--flex-focus-ring);
outline-offset: -4px;
}
}
.flex-date-picker__month-option--selected {
background-color: var(--flex-blue-vivid-60);
color: var(--flex-white);
font-weight: 700;
&:hover {
background-color: var(--flex-blue-vivid-70);
}
}
A digital services project by Flexion