webdevpuneet.com

Time Duration Input — Free HH MM SS Picker JS Snippet

Time Duration Input · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="td-card">
  <label class="td-lbl">Duration</label>
  <div class="td-input" id="td">
    <div class="td-seg" data-unit="h" data-max="99">
      <button type="button" class="td-up" aria-label="Increase hours">+</button>
      <input type="text" inputmode="numeric" value="01" aria-label="Hours">
      <button type="button" class="td-down" aria-label="Decrease hours">\u2212</button>
      <span class="td-cap">hr</span>
    </div>
    <span class="td-colon">:</span>
    <div class="td-seg" data-unit="m" data-max="59">
      <button type="button" class="td-up" aria-label="Increase minutes">+</button>
      <input type="text" inputmode="numeric" value="30" aria-label="Minutes">
      <button type="button" class="td-down" aria-label="Decrease minutes">\u2212</button>
      <span class="td-cap">min</span>
    </div>
    <span class="td-colon">:</span>
    <div class="td-seg" data-unit="s" data-max="59">
      <button type="button" class="td-up" aria-label="Increase seconds">+</button>
      <input type="text" inputmode="numeric" value="00" aria-label="Seconds">
      <button type="button" class="td-down" aria-label="Decrease seconds">\u2212</button>
      <span class="td-cap">sec</span>
    </div>
  </div>
  <div class="td-out" id="tdOut"></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Total-seconds model
One value drives display, rollover, and clamping.
Automatic rollover
59 seconds + 1 carries into minutes with no carry code.
Steppers and arrows
Buttons and arrow keys share one increment path.
Digit sanitizing
Non-numerics stripped, capped at two characters live.
Clamp and pad on blur
Segments settle to valid, zero-padded values.
Dual output
Total seconds plus a formatted HH:MM:SS string.
Symmetric hydrate
setFromTotal() loads an existing duration to edit.
No dependency
Pure HTML/CSS/JS — no date or picker library.

About this UI Snippet

Time Duration Input — HH:MM:SS Picker with Rollover

Screenshot of the Time Duration Input snippet rendered live

A duration input lets a user enter a length of time — for a timer, a video clip, a cooking step, or a booking — as separate hours, minutes, and seconds fields rather than one confusing number. Unlike a clock time picker, it represents an elapsed span and rolls over correctly: bumping seconds past 59 should add a minute. This snippet builds that in plain HTML, CSS, and vanilla JavaScript with no dependency.

One source of truth: total seconds

The trick that makes rollover and clamping trivial is to treat the whole control as a single total-seconds value and derive the display from it. Stepping any segment converts the current fields to a total, adds the segment's weight (3600, 60, or 1), and calls setFromTotal(), which clamps to a max and re-distributes with Math.floor(total/3600), (total%3600)/60, and total%60. So pressing "+" on seconds at 59 naturally carries into minutes — no special-case carry logic anywhere.

Per-segment steppers and arrow keys

Each segment has up and down buttons and listens for ArrowUp/ArrowDown while focused, all routed through the same total-based stepping. That gives mouse and keyboard users the same increment behaviour, and because everything funnels through setFromTotal(), the segments always stay consistent (you can't end up at 01:60:00).

Forgiving typed input

Typing is sanitized live — non-digits are stripped and the value is capped at two characters — but not aggressively reformatted mid-keystroke. On blur each field is clamped to its max (59 for minutes and seconds) and zero-padded, so users can type freely and always land on a valid, padded value like 05.

Live machine-readable output

The readout shows the total in seconds (with toLocaleString() grouping) alongside the padded HH:MM:SS string — the two forms you actually submit or store. Seconds is the canonical value for APIs and <input type="hidden">, while the formatted string is for display.

Reusing the value

Bind totalSeconds() to your form state and you have a clean integer to persist, compare, or feed into a countdown. The same total can hydrate the control on load via setFromTotal(), so editing an existing duration is symmetric with creating one.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSHours, minutes, and seconds segments render with steppers and a total.
  2. 2
    Use the + and - buttonsEach segment steps by its unit and rolls over into the next.
  3. 3
    Type a valueOnly digits are accepted, capped at two per segment.
  4. 4
    Press arrow keysArrowUp and ArrowDown step the focused segment.
  5. 5
    Read the outputThe total seconds and a padded HH:MM:SS string update live.
  6. 6
    Bind the valueUse totalSeconds() as the integer you store or submit.

Real-world uses

Common Use Cases

Timers and alarms
Set a length that feeds a circular countdown, using separate hours, minutes and seconds fields instead of one confusing number.
Video and clip editors
Enter a clip duration beside a video player, with 59 seconds plus one carrying cleanly into the minutes segment.
Booking slot lengths
Pick a slot length beside a time slot picker, clamping values through a single total-seconds model.
Workout and cooking steps
Set step durations next to a pomodoro timer style display, with arrow keys and stepper buttons sharing one increment path.
Numeric stepper pairings
Pair with a quantity stepper for numeric entry, sanitising non-digits and capping each segment at two characters as the user types.

Got questions?

Frequently Asked Questions

The control treats everything as a single total-seconds number. Stepping a segment converts the fields to a total, adds the unit weight (3600, 60, or 1), then setFromTotal() re-derives hours, minutes, and seconds with floor and modulo. So incrementing seconds at 59 automatically adds a minute — there's no manual carry logic, which is where hand-rolled time inputs usually have bugs.

A time picker selects a clock time (like 14:30, a point in the day), while a duration input represents an elapsed length (like 1 hour 30 minutes). Durations need rollover and an unbounded-ish hours field, and the natural output is total seconds rather than a timestamp, which is what this snippet models.

Reformatting on every keystroke fights the caret and makes typing awkward. Instead, input is sanitized to digits live, and on blur each segment is clamped to its max (59 for minutes and seconds) and zero-padded. Users type freely and always end on a valid, consistently padded value.

Store the integer from totalSeconds(). It's compact, easy to compare and sum, and unambiguous across time zones and formats. Render the HH:MM:SS string only for display, and hydrate the control from a stored total with setFromTotal() when editing an existing duration.

Keep a single totalSeconds value in state and derive the three segment displays from it in render. Step handlers update that one number; setFromTotal becomes setState(clamp(newTotal)). Sanitize typed input in the change handler and clamp on blur. The integer maps directly to your form model, and Tailwind handles the segment layout with flex utilities.