Draggable Timeline Range Slider — HTML CSS JS Snippet

Draggable Timeline Range Slider · Forms · Plain HTML, CSS & JS · Live preview

What's included

Features

Continuous ruler-style range picking along a real day axis, not a clickable calendar grid
Day-of-month is the single unit of truth — pixel position is always derived from it, never the reverse
Both handles mutually clamp with a minimum one-day gap, preventing crossing or zero-width ranges
Live human-readable readout ("Mar 3 — Mar 19") generated from the same day numbers driving the handles
Reference tick labels along the track for visual orientation, independent of the draggable range logic
Unified pointer/touch dragging plus full keyboard support with Left/Right arrow keys per handle

About this UI Snippet

Draggable Timeline Range Slider — A Real Axis, Not a Calendar Grid

Screenshot of the Draggable Timeline Range Slider snippet rendered live

A calendar-grid date picker asks you to click two individual dates out of a 30-cell grid. This is a different mental model entirely: the whole month is one continuous *axis*, with two handles you slide along it — closer to how you'd pick a range on a physical ruler than fill in two separate form fields.

Days are the unit, not pixels

Every handle holds a plain integer day-of-month (start, end), and pct(day) = (day / DAYS) * 100 is the only place a day number becomes a screen percentage. Dragging does the reverse: a pointer's clientX becomes a fraction of the track's width, multiplied by DAYS and rounded to the nearest whole day. Because both directions go through the same two formulas, the readout, the handle positions, and the fill bar can never disagree about what day a handle is actually on.

The 1-day gap enforced on both ends

Just like the vertical dual-range slider, start and end clamp against each other — start can't reach or pass end - 1, and vice versa — so the range always has at least one real day of width and the two handles can never land on the exact same day or swap order.

Month tick labels for orientation, independent of the handles

A static row of day labels (1, 8, 15, 22, 30) sits above the track purely for visual reference — it's generated once and never touched again, giving a sense of "where in the month" without being part of the interactive range logic at all.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain why day-of-month is kept as the single source of truth with pixel position always derived from it via pct(), rather than tracking pixel positions directly and converting to days only when needed. It's also worth asking the assistant to extend this to real Date objects spanning multiple months (using a proper date library), or to add a tooltip that follows each handle during drag showing the exact date being hovered before release.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a draggable date-range slider in plain HTML, CSS, and vanilla JavaScript where two handles slide along a continuous track representing days of a month — no calendar grid, no library.

Requirements:
- A horizontal track representing a fixed number of days (e.g. 30), with two independently draggable handle elements representing a range start and range end, plus a fill bar spanning exactly the segment between them.
- The single source of truth for each handle's position must be an integer day-of-month value, not a pixel or percentage value — all visual positioning (handle placement, fill bar width) must be calculated FROM these day integers via one shared formula, and dragging must convert pointer position back into a day integer using the inverse of that same formula, so a day number and its screen position can never disagree.
- The start handle must be prevented from reaching or passing the end handle's current day minus one, and the end handle must be prevented from reaching or passing the start handle's current day plus one — enforced live during dragging, guaranteeing the range always spans at least one real day and the handles can never cross or land on the same day.
- A live, human-readable text readout above the track (e.g. "Mar 3 — Mar 19") that regenerates from the same day integers driving the handles, updating in real time as either handle moves.
- A row of static reference tick labels above or below the track marking several days for visual orientation, generated once and independent of the interactive range state.
- Dragging must work via unified pointer events with a touch event fallback, with touch-action set appropriately to prevent scroll interference during a drag.
- Keyboard support: each handle independently focusable, responding to Left/Right arrow keys to move by one day (respecting the same never-cross constraint as dragging).

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="dtr-wrap">
  <label class="dtr-label">Filter by date range</label>
  <div class="dtr-readout"><span id="dtrStart">Mar 3</span> — <span id="dtrEnd">Mar 19</span></div>
  <div class="dtr-track" id="dtrTrack">
    <div class="dtr-months" id="dtrMonths"></div>
    <div class="dtr-fill" id="dtrFill"></div>
    <div class="dtr-handle" id="dtrHandleStart" tabindex="0" role="slider" aria-label="Range start"></div>
    <div class="dtr-handle" id="dtrHandleEnd" tabindex="0" role="slider" aria-label="Range end"></div>
  </div>
</div>

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA track appears spanning a full month, with two handles set to March 3 and March 19.
  2. 2
    Drag either handleIt slides along the axis and clamps a day short of the other handle — never crossing it.
  3. 3
    Watch the readoutThe "Mar 3 — Mar 19" text above the track updates live as you drag.
  4. 4
    Use arrow keysFocus a handle and press Left/Right to nudge it one day at a time.
  5. 5
    Adapt the monthChange DAYS and MONTH_LABEL in the JS to represent any month or any numeric range at all.

Real-world uses

Common Use Cases

Booking or availability date-range filters
Let a visitor drag a stay length or booking window directly on a visual month axis.
Analytics dashboard date-range selectors
A faster way to scope a report window than clicking two separate calendar dates.
Event or campaign scheduling ranges
Set a campaign's start and end within a month with an immediate visual sense of duration.
Content publishing or embargo windows
Define a visible/hidden window for content with a single intuitive drag interaction.

Got questions?

Frequently Asked Questions

Change the DAYS constant to match, and update the tick label array passed into the month-label generation loop to reflect that month's actual day count.

Keep start/end as day-of-month integers internally (all the range math depends on this), and only convert them to real Date objects or formatted strings at the point where fmt() currently builds the readout string, using your date library or Intl.DateTimeFormat of choice.

Yes — change DAYS to the total number of days across the full span (e.g. 90 for a rolling quarter) and adjust the month tick labels to mark month boundaries instead of just day-of-month numbers.

Without it, both handles could land on the identical day, producing a zero-length "range" that's ambiguous to interpret — clamping start below end (and vice versa) by at least one day guarantees every selection is a real, non-empty range.

Both handles carry role="slider" with a descriptive aria-label and a live aria-valuetext reflecting the human-readable date, and are independently focusable and operable via Left/Right arrow keys without a mouse.