Time Picker
A Time Picker is a form element used to select a time.
const LabelOnlyPicker = (props) => {const uidTP = useUID();return (<><Label htmlFor={uidTP}>What time is it locally?</Label><TimePicker id={uidTP} {...props} /></>);};render(<LabelOnlyPicker/>)
The Time Picker is an input field which accepts a value in HH:mm
format.
Currently, the Time Picker is built on top of the HTML time picker, using an input
field with type="time"
, which comes with some inherent limitations.
In order to release a functional and stable Time Picker for immediate use, we decided to work within these limitations with the intention of updating this package to a custom Time Picker in a future release. The current API was designed to avoid any breaking changes with future versions of the Time Picker.
It is used with the Label and Help Text components.
Because browsers' implementations of the native time picker vary, this component may not be fully accessible in all cases. Chrome/Edge, Safari and Firefox all support the Time Picker, but the user experience differs browser-to-browser. Some of those differences are outlined below:
Chrome/Edge
| |
Safari
| |
Firefox
|
While cross-browser functionality, style and accessibility differences are present, they will not affect the usability of the Time Picker. The API is stable and full usage of the component is encouraged. There are certain use cases not included in the input type="time"
functionality, such as a time range picker. You can find examples of how to recreate those use cases below. If there's a Time Picker use case not covered by the current implementation, feel free to open a Discussion so that we can help you find a solution.
- Include a visible label on all Time Pickers.
- Each label must use the
htmlFor
prop that matches theid
of the associated input. - Use one of 3 ways to label a Time Picker:
- Visible label with Label (preferred)
- Visible label that's associated to the Time Picker with
aria-labelledby
- Label directly using
aria-label
- Provide clear identification of required fields in the label or at the start of a form. If you use the required field indicator, include the form key at the start of the form.
- Use the
required
prop to programatically indicate they are required to browsers.
- Use the
- Include inline error text with the error icon on any field that errors to make it visually clear that the field changed.
- If the Time Picker has associated Help Text or error text, include the
aria-describedby
prop on the Time Picker. This should match theid
of the help or error text.
The Time Picker component should include the base TimePicker, along with supporting elements to compose an input field that gives users the context they need to successfully fill it out.
- Label — A label should be included for every Time Picker and provide a short title for its associated input.
- Required field indicator — In a form where there are fewer or as many required fields as optional, use a required indicator to show users which fields are required to be filled out.
- Help text — Text that's placed below the field to help users prevent an error and describe what makes the Time Picker input successful.
Make sure to connect the Label
to the TimePicker
. This is done with the htmlFor
prop on the label, and the id
prop on the Time Picker. Those two need to match.
If the Time Picker has any associated Help Text, it should also use the aria-describedby
prop that equals the id
of the Help Text. This ensures screen readers know the Help Text ties directly to the Time Picker.
const RequiredTimePicker = (props) => {const uidTP = useUID();const uidHT = useUID();return (<><Label htmlFor={uidTP} required>What time is your workday over?</Label><TimePicker required id={uidTP} aria-describedby={uidHT} {...props} /><HelpText id={uidHT}>Select a time.</HelpText></>);};render(<RequiredTimePicker/>)
The Time Picker doesn't currently support the selection of time ranges within a single input field, however a time range picker can be easily implemented using two side-by-side Time Pickers.
const TimeRangePicker = (props) => {const startUidTP = useUID();const endUidTP = useUID();return (<Stack orientation="horizontal" spacing="space80"><Box><Label htmlFor={startUidTP}>Start time</Label><TimePicker id={startUidTP} {...props} /></Box><Box><Label htmlFor={endUidTP}>End time</Label><TimePicker id={endUidTP} {...props} /></Box></Stack>);};render(<TimeRangePicker/>)
Paste pickers don't currently support date time range selections in a single component, however the same functionality can be implemented using a combobox along with the date range and time range picker solutions. If you'd like to provide your users with pre-determined options before displaying the pickers, use the following example as a jumping off point.
const DateTimeRangePicker = (props) => {const [selectedItem, setSelectedItem] = React.useState('')const uidStartDP = useUID();return selectedItem === "Custom" ? (<><Box margin="space60"><Label htmlFor="uidStartDP">Date range</Label></Box><Stack orientation="horizontal" spacing="space80"><Box marginLeft="space60"><DatePicker id="uidStartDP" aria-describedby="start-date" /><HelpText id="start-date">Start date</HelpText></Box><Box><TimePicker aria-describedby="start-time" /><HelpText id="start-time">Start time</HelpText></Box></Stack><Stack orientation="horizontal" spacing="space80"><Box marginLeft="space60" marginTop="space60"><DatePicker aria-describedby="end-date" /><HelpText id="end-date">End date</HelpText></Box><Box marginTop="space60"><TimePicker aria-describedby="end-time" /><HelpText id="end-time">End time</HelpText></Box></Stack></>) : (<Box margin="space60"><ComboboxinsertAfter={<CalendarIcon color="colorTextIcon" decorative />}items={["Last 7 days", "Last 30 days", "Last 6 months", "Custom"]}labelText="Date range"onSelectedItemChange={(changes) => {setSelectedItem(changes.selectedItem)}}/></Box>)}render(<DateTimeRangePicker />)
In addition to the Time Picker, this package exports a formatReturnTime()
function that can be used to format the time value that's being returned from the Time Picker.
value
format, however, which is returned in HH:mm
format (using a 24-hour time format), can be changed.To change the format of the return time value, we recommend using the formatReturnTime()
function on the Time Picker's onChange()
or onBlur()
handler.
The function accepts two parameters: (1) the time value that should be formatted (e.g. '14:52'
), and (2) the desired time format (e.g. 'mm:hh aa'
). It uses a library called date-fns to return the given time in the desired format. Time format must follow the tokens outlined by date-fns.
const OnChangePicker = (props) => {const [value, setValue] = React.useState('');const [timeFormat, setTimeFormat] = React.useState('HH:mm');const uidTP = useUID();const uidHT = useUID();const timeFormatOptions = ['hh:mm aa', 'hh:mm a', 'HH:mm'];const handleChange = (val, format) => {setValue(formatReturnTime(val, format));return value;};return (<><Box marginBottom="space60"><Comboboxitems={timeFormatOptions}labelText="Return time format:"onInputValueChange={({inputValue}) => {if (inputValue !== undefined) setTimeFormat(inputValue);}}/></Box><Label htmlFor={uidTP}>What time were you born?</Label><TimePickerid={uidTP}aria-describedby={uidHT}onChange={(evt) => {handleChange(evt.target.value, timeFormat);}}{...props}/><HelpText id={uidHT}>Select a time above.</HelpText><Box marginTop="space60">{' '}<b>Return value:</b> {value}{' '}</Box></>);};render(<OnChangePicker/>)
Change a Time Picker to its error state and add an inline error if the value entered doesn't pass validation requirements.
Use Help Text to show an inline error below the field to inform a user of any errors in their value. Change the Help Text to variant=“error”
and add error copy before the original help text copy.
const HasErrorPicker = (props) => {const uidTP = useUID();const uidHT = useUID();return (<><Label htmlFor={uidTP}>When does your workday start?</Label><TimePicker id={uidTP} aria-describedby={uidHT} hasError {...props} /><HelpText id={uidHT} variant="error">Select a time.</HelpText></>);};render(<HasErrorPicker/>)
Use a disabled Time Picker to show users that they can't interact with it.
If you want to show information that can't be edited, use a read-only field or consider another way of showing static information.
const DisabledPicker = (props) => {const uidTP = useUID();const uidHT = useUID();return (<><Label htmlFor={uidTP} disabled>Set a curfew for your child</Label><TimePicker id={uidTP} aria-describedby={uidHT} disabled {...props} /><HelpText id={uidHT}>Choose a time.</HelpText></>);};render(<DisabledPicker/>)
Use a read-only Time Picker when a its value can't be changed, but users should be able to read or apply focus on it.
const ReadOnlyPicker = (props) => {const uidTP = useUID();const uidHT = useUID();return (<><Label htmlFor={uidTP}>What's your favorite time of day?</Label><TimePicker id={uidTP} aria-describedby={uidHT} readOnly value="13:00" {...props} /><HelpText id={uidHT}>Choose a time.</HelpText></>);};render(<ReadOnlyPicker/>)
A Time Picker must have at least a label and an input.
const LabelOnlyPicker = (props) => {const uidTP = useUID();return (<><Label htmlFor={uidTP}>What time is it locally?</Label><TimePicker id={uidTP} {...props} /></>);};render(<LabelOnlyPicker/>)
Quick note about Time Picker DOM methods
There are three methods that come with the HTML picker: stepUp()
, stepDown()
and select()
. Usage of these methods is possible, but we do not encourage it, as they may not be supported in future versions of the Time Picker. Use at your own risk!
When using multiple Time Pickers, stack them vertically with $space-80
spacing between each field. To stack them, you can use either a Box or a Stack (as seen in the example below).
const StackOfPickers = (props) => {const uidTPOne = useUID();const uidHTOne = useUID();const uidTPTwo = useUID();const uidHTTwo = useUID();return (<Stack orientation="vertical" spacing="space80"><Box><Label htmlFor={uidTPOne}>What time is it now?</Label><TimePicker id={uidTPOne} aria-describedby={uidHTOne} {...props} /><HelpText id={uidHTOne}>Select a time above.</HelpText></Box><Box><Label htmlFor={uidTPTwo}>What time was it 5 minutes ago?</Label><TimePicker id={uidTPTwo} aria-describedby={uidHTTwo} {...props} /><HelpText id={uidHTTwo}>Select a time above.</HelpText></Box></Stack>);};render(<StackOfPickers/>)
Avoid placing multiple pickers on the same horizontal row to help make it easier to scan a page vertically. Use a Grid if you need to place them horizontally.
Labels should clearly describe the time value being requested. They should be concise and descriptive, not instructional. To do this:
- Use Help Text to provide instruction if needed. For example, don't use "Enter the date you wish to receive your bill below" as label text. Instead, use "Billing date" as a label and "Your account will be automatically billed on the above date." as Help Text.
- Avoid articles ("a", "the") and punctuation. For example, use "SIM registration code" rather than "The SIM registration code".
To support internationalization, avoid starting a sentence with the label and using the field to finish it since sentence structures vary across languages. For example, use "Call log expiration time" or "How long should logs be stored?". Don't use "Remove logs after:".
Use required indicators to show users which fields they must fill out.
const RequiredTimePicker = (props) => {const uidTP = useUID();const uidHT = useUID();return (<><Label htmlFor={uidTP} required>What time is your workday over?</Label><TimePicker required id={uidTP} aria-describedby={uidHT} {...props} /><HelpText id={uidHT}>Select a time.</HelpText></>);};render(<RequiredTimePicker/>)
Validate Time Picker fields on form submission.
Validating a field input when the user leaves the current field (on blur) can be helpful to check for syntax errors. However, this can be frustrating to users who tab through controls to navigate a page, and to screen reader users, who might not be able to detect that an error occurred on blur.
Don't prevent form submission by disabling the submit button. A disabled button cannot be detected by assistive technologies. Use error messages to explain what information is missing or incorrect.
Usage of the min
and max
properties on Time Picker is discouraged as browser support is varied and limited.
Use inline error text to explain how to fix an error. For additional guidance on how to compose error messages, refer to the error state pattern.
Use Help Text to help users avoid errors.
Situation | Recommended phrasing |
---|---|
User didn't pick a time | Select a time. |
The end time is before the start date | The end time occurs before the start time. |
The start date is after the end date | The start time occurs after the end time. |
Time Picker and placeholder
Because Time Picker has a default display value of --:-- --
(and defaults to 12:30 PM
on Safari), any value that gets passed into placeholder
will be overwritten (and effectively ignored).