Date Picker
The calendar view should use button semantics for days, with grid semantics for keyboard navigation and announcements.
Quarantine
A Date Picker is a form input component that lets users enter or select a date. It combines a text field (for direct entry) with a calendar popup (for visual selection), so users can complete the task with either keyboard-first or pointer-first behaviour. The component is fully responsive and designed to be used across mobile, tablet, and desktop without separate variants.
Anatomy
Section titled “Anatomy”- Label
- Input field
- Calendar trigger
- Helper text
- Calendar popup
When to use it
Section titled “When to use it”Use the Date Picker when:
- The user needs to select a specific calendar date (for example, a preferred test-drive day).
- The date is in the near future or recent past and calendar context improves selection confidence.
- Input must be constrained to a valid date range (min/max dates).
- Date format consistency is required for downstream processing.
Avoid the Date Picker when:
- Only a month or a year is required.
- The interaction is relative rather than absolute (for example, “in X days”).
- There is no persistent, visible label associated with the field.
Properties
Section titled “Properties”State
Rested, Hover, Active, Disabled, Focus, and Error. Active opens the calendar while keeping typed input available; Focus highlights the currently edited field for keyboard users without opening the popup; Error appears after blur when the value is invalid.
Required
A boolean that marks the field as mandatory. The label reflects the required state visually and through the form submission semantics.
Helper text
Optional text beneath the field — use it to show the expected date format with a concrete example. The component swaps in an error message in the same slot when validation fails.
Calendar icon
A trailing calendar icon doubles as the trigger that opens the popup. It defaults off in the rested state and on in the error variant so the error indicator can take its place.
Calendar popup
The month/year grid that opens on click or tap. Day cells expose full dates via aria-label, disabled or unavailable days remain visible but non-selectable, and the current day carries a distinct outline.
Platform considerations
Section titled “Platform considerations”Desktop
The calendar opens beneath the input. Keyboard interaction covers the field, the trigger, month navigation, and day cells — focus moves into the popup when it opens and returns to the trigger on close.
Tablet
Same behaviour as desktop. Make sure the popup has enough room to render below the input without clipping; flip above when needed.
Mobile
Keep both paths available — typed input shouldn’t disappear in favour of calendar-only on smaller screens. The calendar popup may shift to a sheet pattern but still respects focus and aria-label semantics.
Best practices
Section titled “Best practices”Treat the picker as a dual-path control — typing and tapping should both work all the way through.
Do
Keep both interaction paths available (typed input and calendar selection), validate on blur and provide a clear error message that includes the expected format, use helper text to show a concrete example date, and keep disabled and unavailable dates visible but non-selectable inside the calendar.
Don't
Don’t hide or replace the persistent label with placeholder-only text, don’t rely on colour alone for the error or selected state (pair it with an icon or copy), don’t force calendar-only interaction for keyboard and assistive-tech users, and don’t trigger validation errors while users are still typing.
Content guidelines
Section titled “Content guidelines”Make the label specific to the date you’re asking for (“Preferred test-drive date”, not just “Date”). Use the helper text to demonstrate the expected format with a real example (“DD/MM/YYYY — 25/12/2026”) so the user can match the pattern before the picker opens. Error copy should name the rule that failed (“Pick a date after today”) rather than restating the field — pair it with the error icon so the message reads without colour alone.
Styles
Section titled “Styles”The calendar view should use button semantics for days, with grid semantics for keyboard navigation and announcements.
<div class="tng-calendar"> <div class="tng-calendar-header"> <button class="tng-icon-button is-ghost"> <i class="tng-icon icon-chevron-left" aria-hidden="true"></i> </button> <h2 class="tng-text-body">March 2026</h2> <button class="tng-icon-button is-ghost"> <i class="tng-icon icon-chevron-right" aria-hidden="true"></i> </button> </div> <div class="tng-calendar-weekdays"> <div>Mon</div> <div>Tue</div> <div>Wed</div> <div>Thu</div> <div>Fri</div> <div>Sat</div> <div>Sun</div> </div> <div class="tng-calendar-week"> <button disabled>23</button> <button disabled>24</button> <button disabled>25</button> <button disabled>26</button> <button disabled>27</button> <button disabled>28</button> <button>1</button> </div> <div class="tng-calendar-week"> <button>2</button> <button>3</button> <button>4</button> <button>5</button> <button>6</button> <button>7</button> <button>8</button> </div> <div class="tng-calendar-week"> <button>9</button> <button>10</button> <button class="is-selected">11</button> <button>12</button> <button>13</button> <button>14</button> <button>15</button> </div> <div class="tng-calendar-week"> <button>16</button> <button>17</button> <button>18</button> <button>19</button> <button>20</button> <button>21</button> <button>22</button> </div> <div class="tng-calendar-week"> <button>23</button> <button>24</button> <button>25</button> <button class="is-today">26</button> <button>27</button> <button>28</button> <button>29</button> </div> <div class="tng-calendar-week"> <button>30</button> <button>31</button> <button disabled>1</button> <button disabled>2</button> <button disabled>3</button> <button disabled>4</button> <button disabled>5</button> </div></div>March 2026
Recipe
Section titled “Recipe”March 2026
For designers
Section titled “For designers”- Keep labels visible and outside the field in every state.
- Keep format guidance (for example,
DD/MM/YYYY) visible in helper text at all times. - Treat the calendar trigger as an interactive button with a minimum 44x44px target.
- Maintain visible focus across field, trigger, month controls, and day cells, including high-contrast mode.
- Pair error color with icon and descriptive message, never color alone.
For developers
Section titled “For developers”The date picker follows the WAI-ARIA Date Picker Dialog pattern.
To ensure the Date Picker meets WCAG 2.1 AA and supports assistive technologies, follow these implementation standards:
- Text input is mandatory
- Do not remove typed date entry.
- Users must be able to complete the task without traversing day-by-day cells.
- Labels and descriptions
- Associate input and label with
for/id. - Link helper text and error text through
aria-describedby. - Announce error updates with
aria-live="polite".
- Associate input and label with
- Calendar semantics
- Use a
<button>for trigger and for each day cell. - Use
aria-expandedon trigger. - Expose day metadata with
aria-label,aria-selected,aria-disabled, andaria-current="date".
- Use a
- Focus and keyboard behavior
- Trap focus while popup is open.
- On open, move focus to selected date or current month context.
- On close (selection, Escape, outside click), return focus to trigger.
- Arrow keys move days, Enter/Space selects, Escape closes, PageUp/PageDown switches month.
- Tab closes popup and moves to the next page focus target.
- Mobile input mode
- Use
inputmode="numeric"to surface the numeric keyboard on mobile.
- Use
Roles and attributes
Section titled “Roles and attributes”| Element | Attributes |
|---|---|
| Trigger button | aria-haspopup="dialog", aria-expanded, aria-controls, and an accessible name (for example, aria-label="Choose date"). |
| Popup container | role="dialog", clear labeling via aria-label or aria-labelledby. |
| Calendar grid | role="grid" and aria-labelledby pointing to the visible month/year heading. |
| Week rows | role="row" |
| Day cells / button | Cell wrapper with role="gridcell"; day as a native <button>. Use aria-selected="true" for the chosen date and aria-current="date" for today. |
Keyboard interaction
Section titled “Keyboard interaction”| Key | Action |
|---|---|
| Enter / Space | Open picker from trigger, or select focused day |
| Arrow keys | Move day focus in the calendar grid |
| Home / End | Move focus to first / last focusable day |
| Escape | Close the popup and return focus to the trigger button |
Announcements
Section titled “Announcements”- Update the trigger label to the selected date after selection.
- Keep month/year text connected to the grid with
aria-labelledbyso context is announced while navigating. - Ensure disabled days are not focusable and keep native
disabledsemantics.