Example · Features
Open From a Button
Open the picker from an icon button next to the field. The field stays a normal text input, so the time can also be typed.
Version
This setup needs timepicker-ui 4.5.0 or later.
Icon on the Right (12h)
Pass the wrapper to the constructor, not the input. The wrapper becomes the picker root, and the data-open button inside it opens the picker. A click on the field does not.
Preview
HTML
index.htmlhtml
<label for="meeting-time">Meeting time</label><div class="time-field" id="meeting-time-field"><input id="meeting-time" type="text" /><button type="button" data-open="meeting-time" aria-label="Choose time"><svg aria-hidden="true"><!-- clock icon --></svg></button></div>
JavaScript
index.tstypescript
import { TimepickerUI } from 'timepicker-ui';const wrapper = document.querySelector('#meeting-time-field');const picker = new TimepickerUI(wrapper, {behavior: { returnFocus: true }});picker.create();
Icon on the Left (24h)
The same setup with clock: { type: '24h' }. The button comes before the input in the markup, so the Tab order matches what you see.
Preview
HTML
index.htmlhtml
<label for="departure-time">Departure time</label><div class="time-field time-field--icon-left" id="departure-time-field"><button type="button" data-open="departure-time" aria-label="Choose time"><svg aria-hidden="true"><!-- clock icon --></svg></button><input id="departure-time" type="text" /></div>
JavaScript
index.tstypescript
import { TimepickerUI } from 'timepicker-ui';const wrapper = document.querySelector('#departure-time-field');const picker = new TimepickerUI(wrapper, {clock: { type: '24h' },behavior: { returnFocus: true }});picker.create();
Placing the Icon
Position the button with your own CSS. For example:
index.csscss
.time-field {position: relative;}.time-field input {width: 100%;padding-right: 3rem;}.time-field button {position: absolute;top: 50%;right: 0.25rem;transform: translateY(-50%);}.time-field--icon-left input {padding-right: 1rem;padding-left: 3rem;}.time-field--icon-left button {right: auto;left: 0.25rem;}
React
Put a ref on the wrapper and create the picker in an effect, so it only runs in the browser:
index.tsxtsx
import { useEffect, useId, useRef } from 'react';import { TimepickerUI } from 'timepicker-ui';import { Clock } from 'lucide-react';export function MeetingTimeField() {const id = useId();const wrapperRef = useRef<HTMLDivElement>(null);useEffect(() => {if (!wrapperRef.current) return;const picker = new TimepickerUI(wrapperRef.current, {behavior: { returnFocus: true }});picker.create();return () => picker.destroy();}, []);return (<><label htmlFor={id}>Meeting time</label><div ref={wrapperRef} className="time-field"><input id={id} type="text" /><button type="button" data-open="meeting-time" aria-label="Choose time"><Clock aria-hidden="true" /></button></div></>);}
Notes
- Only the button opens the picker. The field can be typed in, and the picker reads the typed value when it opens.
- Use a real
<button type="button">. It also opens the picker with Enter and Space, and inside a formtype="button"keeps it from submitting the form. - An icon-only button has no text, so give it an
aria-labelsuch as"Choose time". behavior.returnFocus: truemoves focus back to the field after the picker closes.- Every element in the wrapper with the same
data-openvalue as the first one opens the picker.