webdevpuneet.com

Discrete Snap-Stop Slider — HTML CSS JS Snippet

Discrete Snap-Stop Slider · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="dss-wrap">
  <label class="dss-label">Shipping speed</label>
  <div class="dss-track-wrap">
    <div class="dss-track">
      <div class="dss-fill" id="dssFill"></div>
      <button class="dss-stop" data-i="0" style="left:0%"></button>
      <button class="dss-stop" data-i="1" style="left:33.33%"></button>
      <button class="dss-stop" data-i="2" style="left:66.66%"></button>
      <button class="dss-stop" data-i="3" style="left:100%"></button>
      <div class="dss-thumb" id="dssThumb" tabindex="0" role="slider" aria-valuemin="0" aria-valuemax="3" aria-valuenow="0"></div>
    </div>
  </div>
  <div class="dss-ticks" id="dssTicks"></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), 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

Holds a discrete index, not a continuous value — physically cannot land between two labeled stops
Every input method (drag, click stop, click label, arrow key) converges on one setIndex function
Dragging rounds live pointer position to the nearest stop, with the thumb easing smoothly to it
Clickable stop dots and clickable text labels, both jumping directly to that exact position
Full keyboard support — Left/Right/Up/Down step one stop, Home/End jump to the first/last
Real ARIA slider semantics with aria-valuetext announcing the current label, not just a raw number

About this UI Snippet

Discrete Snap-Stop Slider — An Index, Not a Continuous Value

Screenshot of the Discrete Snap-Stop Slider snippet rendered live

A price-range slider outputs any number between its min and max. This one is a completely different kind of control: it holds an *index* into a fixed list of labeled options — Standard, Express, Overnight, Same-Day — and can only ever land on exactly one of those four positions. There is no such thing as "index 1.5" here; every interaction, whatever triggers it, ends by calling setIndex() with a whole number.

Percentage is derived from the index, never the reverse

render() computes the thumb's visual position with one line: pct = (current / (count - 1)) * 100. Dragging works the opposite direction — it converts a raw pointer position into a percentage, then *rounds* that percentage into the nearest whole index with Math.round(pct * (count - 1)), and only that rounded index is ever written to current. The thumb visually eases to the exact stop position via a CSS transition on left, but the underlying state was never anything other than a clean integer.

Every input path converges on one function

Clicking a stop dot, clicking a text label, dragging the thumb, and pressing an arrow key are four completely different gestures, but every one of them ends by calling setIndex(i) — which is the only place that clamps the value into range and re-renders. That's what guarantees the slider can never visually or logically land between two stops, no matter which input method was used.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA 4-stop slider appears, set to "Standard" with the label highlighted below it.
  2. 2
    Drag the thumbIt follows your cursor smoothly, but always eases to and settles on the nearest labeled stop — never in between.
  3. 3
    Click a dot or labelJump straight to that exact stop with a smooth transition.
  4. 4
    Use arrow keysFocus the thumb and press Left/Right to move one stop at a time; Home/End jump to the first or last.
  5. 5
    Add a fifth tierAdd one label to LABELS and one .dss-stop button positioned at its new percentage — the math adjusts automatically.

Real-world uses

Common Use Cases

Shipping speed selectors
The exact pattern this snippet models — letting a shopper pick a tier, not an arbitrary number.
Plan or tier pickers
Slide between Free/Pro/Team/Enterprise as clean discrete steps rather than a dropdown.
Difficulty or intensity settings
Easy/Medium/Hard/Expert — any setting that's inherently a small fixed set of named options.
Multi-step progress selectors
Let a user jump directly to a specific onboarding or wizard step by dragging or clicking.

Got questions?

Frequently Asked Questions

Add one string to the LABELS array and one matching .dss-stop button in the HTML, positioned at its new even percentage (100 / (new count - 1) per step) — count updates automatically from LABELS.length.

Yes — give each .dss-stop button a custom left percentage instead of even spacing, and replace the render() percentage formula with a lookup table mapping each index to its actual custom percentage.

Rounding live (during the drag, not just at the end) is what makes the fill bar and any live label always show the stop you're about to land on, giving immediate feedback rather than a jarring correction only at release.

LABELS[current] gives the selected label at any time; hook a callback or custom event dispatch inside setIndex if you need to react to changes elsewhere in a real application.

Yes — the thumb carries role="slider" with aria-valuemin/max/now plus aria-valuetext for the human-readable label, and is fully operable via Left/Right/Up/Down/Home/End without a mouse.