PickTime v3

A time picker for any form, in any framework.

One custom element. No framework wrapper, no stylesheet to import, no hidden input to keep in sync.

booking.html
<form>
  <label for="meeting">Meeting time</label>
  <pick-time id="meeting" name="meeting" value="09:30"></pick-time>
  <button>Book</button>
</form>

<script type="module">
  import 'picktime';

  form.addEventListener('submit', e => {
    new FormData(form).get('meeting'); // "09:30"
  });
</script>

01 / install

Add it in one line

One dependency, one import. That import registers <pick-time> and carries its styles with it.

1 Install

npm install picktime

2 Import once

import 'picktime';

3 Use it

<pick-time name="start"></pick-time>

Chrome 114+, Edge 114+, Firefox 125+, Safari 17+. No build step required for the CDN route.

02 / how to use

Drop it into anything

It is a custom element, so there is nothing to wrap. Pick your stack and copy the block.

<script type="module" src="https://cdn.jsdelivr.net/npm/picktime/+esm"></script>

<form>
  <label for="start">Start</label>
  <pick-time id="start" name="start" value="09:30"></pick-time>
  <button>Submit</button>
</form>

Nothing else. The form submits start=09:30.

03 / playground

Try every option

Change an attribute, watch the element react. Nothing is faked.

Attributes

04 / the point

It is a real form control

Form-associated through ElementInternals. No hidden input, no serialiser, no submit handler doing cleanup. required, min and max block submission the way the platform does it.

Submit to see the FormData payload.

05 / accessibility

Built for the keyboard

Each segment is an ARIA spinbutton that announces its value and range. Tab is trapped inside the panel while it is open, and focus returns to the field when it closes.

Tab cycles hours, minutes, seconds, AM, PM, then wraps. It never escapes to the page behind.

↓
Open the panel from the field
↑ ↓
Step the focused segment
Page Up
Step in larger jumps
Home End
Jump to the segment bounds
Tab
Cycle segments, trapped in the panel
Esc
Close and return focus

06 / input

Type what you actually type

The field is not readonly. Parsing is lenient, and anything unparseable sets badInput rather than throwing.

Nothing entered yet.
  • 9:30 pm21:30
  • 930pm21:30
  • 093009:30
  • 21:3021:30
  • 9.3009:30
  • 12am00:00

07 / i18n

The locale decides, not you

Hour cycle, day-period labels and formatting all come from Intl. Omit hour-cycle and each locale gets what it expects.

en-US
de-DE
ja-JP
en-GB

08 / rendering

Nothing can clip it

The panel is a real popover, so it renders in the top layer. An overflow: hidden ancestor and a stacking context both lose. Escape and click-outside come free with it.

09 / theming

Yours to restyle

Styles live in a shadow root, so your CSS cannot break it and it cannot leak into you. Thirty-six custom properties are the seams, and they are the same names v2 used, so an existing theme ports across by moving it from a class onto the element.

Click the field

pick-time.kd {
  /* Main */
  --pt-background: #274bce;
  --pt-foreground: #fff;

  /* Panel */
  --pt-padding: 10px;
  --pt-border-radius: 12px;
  --pt-border-color: #1b338c;
  --pt-box-shadow-color: rgb(0 0 0 / 0.2);

  /* Segments */
  --pt-input-color: var(--pt-foreground);
  --pt-input-background: var(--pt-background);
  --pt-input-font-size: 30px;
  --pt-input-border-radius: 10px;
  --pt-input-border-color: #1b338c;
  --pt-input-focus-border-color: var(--pt-foreground);
  --pt-input-focus-background: #213fad;

  /* Dots */
  --pt-dots-color: #1b338c;

  /* Meridiem */
  --pt-meridiem-font-size: 10px;
  --pt-meridiem-border-radius: 50%;
  --pt-meridiem-color: var(--pt-foreground);
  --pt-meridiem-hover-color: var(--pt-foreground);
  --pt-meridiem-hover-background: #213fad;
  --pt-meridiem-border-color: var(--pt-input-border-color);
  --pt-meridiem-checked-color: var(--pt-foreground);
  --pt-meridiem-checked-background: #ff6e46;
  --pt-meridiem-checked-border-color: var(--pt-foreground);

  /* Trigger, kept neutral so only the panel carries the theme */
  --pt-field-background: #fff;
  --pt-field-color: #1b338c;
  --pt-field-border-color: #cbd6f5;
  --pt-field-border-radius: 10px;
}

Colour and spacing come from the properties above. When you need to reach a specific piece of the structure, target its part:

::part(input)
The text field you click or type into
::part(picker)
The floating panel itself
::part(spin)
Every number segment at once
::part(hours)
Just the hours segment
::part(minutes)
Just the minutes segment
::part(seconds)
Just the seconds segment
::part(separator)
The dots between segments
::part(meridiem)
The AM/PM group
::part(period)
A single AM or PM button
::part(arrow)
The pointer joining panel to field
pick-time::part(picker) {
  box-shadow: 0 12px 40px rgb(0 0 0 / 0.25);
}

pick-time::part(hours) {
  font-variant-numeric: tabular-nums slashed-zero;
}