Glassmorphism✨ Premium

Neu Slider

Real input type=range sliders dressed in neumorphism: a 10 px carved track, a violet gradient fill that stops right under the handle, a raised 24 px handle, value shown above. Mouse, finger and keyboard, 13 lines of JavaScript.

CSSSliderUI

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Glassmorphism has 47 effects, including 6 free. Explore the category →

3 usage examples

Volume and bass of a living-room speaker — the speaker's app — Neu Slider example 1

① Volume and bass of a living-room speaker — the speaker's app

WhenThe user sets the volume, then raises the bass of their speaker from the app while the Sunday playlist plays.
WhyThe raised handle recalls the faders of a mixing desk, and the percentage says where you are without counting notches. Two sliders for two settings of the same sound.
SettingsTwo 270 px sliders (Volume 62%, Bass 25%), titles in #374151 and values in #6d28d9 (5.6:1). Raised/carved presets under the sliders, same material; speaker on a shelf to the right.
Extraction power — touch panel of a kitchen hood — Neu Slider example 2

② Extraction power — touch panel of a kitchen hood

WhenWhile cooking, you raise the extraction with a fingertip and dim the hob light.
WhyA 420 px carved track is easy to hit even with damp fingers, and the hood's light steel front already has the material's tint: the panel blends in without a frame.
SettingsSliders widened to 420 px (the track and the fill formula follow the width), icons on the left. Extraction from 10% to 82% on touch, light at 17%: in the capture, the value jumps to where it was touched.
Brightness and color temperature — connected desk lamp — Neu Slider example 3

③ Brightness and color temperature — connected desk lamp

WhenIn the evening, the student switches the lamp to warm light; brightness stays at 70%.
WhyTwo stacked sliders for two settings of one object; the relief extends the lamp's white stand, and the amber fill of the second says “warm” before you read the number.
Settings228 px sliders. The second one's fill is overridden with linear-gradient(90deg, #fbbf24, #f97316) on both tracks (-webkit- and -moz-), with the same width formula; “Cool / Warm” scale below. A scene script tints the lamp's glow from both values.

How it works

Each slider is an <input type="range" class="neu-slider"> inside a <label class="neu-slider-group"> that holds the title and the value. The native look is removed (appearance: none) and redrawn on each engine's pseudo-elements: the track (::-webkit-slider-runnable-track, ::-moz-range-track) is 10 px tall, border-radius: 5px, carved by #b8bec7 2px 2px 4px inset and #fff -2px -2px 4px inset; the handle (::-webkit-slider-thumb, ::-moz-range-thumb) is a 24 px raised disc (4 px / 8 px shadows, 6 px / 12 px on hover), with a grab then grabbing mouse cursor. The input itself is 24 px tall with a transparent background: the full height of the handle is a grab area.

The fill is a second background on the track: linear-gradient(135deg, #8b5cf6, #a78bfa) sized calc(12px + (100% - 24px) * var(--neu-p)). The handle's center travels from 12 px to the width minus 12 px; with this formula the gradient stops exactly under that center, at 0% as at 100%. The JavaScript is 13 lines: on every input event, it writes the ratio into --neu-p and the value into .neu-slider-value (“75%”). It applies to every .neu-slider on the page, with no id.

Why real <input type="range"> elements rather than drawn <div>s: a slider made of blocks set to 75% and 40% by an inline style promises a grip (cursor: grab) but does not move — dragging its handle 120 px leaves “75%”, and Tab finds nothing to reach. The native input brings dragging, touch, keyboard and screen-reader announcements for free; the CSS only dresses it. Measured in Chromium, Firefox and WebKit: dragging left from 75% gives 28%, a tap on the track at 20% gives 20%, arrow keys step by one, End and Home go to 100% and 0%. As for the whole family, .neu-slider-scene carries its surface (#e0e5ec, padding: 28px, border-radius: 24px).

Accessibility

  • Keyboard and screen readers: these are native input type="range" elements. Tab reaches them, arrows change the value by one, Page Up/Down by ten, Home and End go to the bounds. The accessible name comes from the wrapping label (“Volume”, “Luminosité”); the displayed value carries aria-hidden="true" so it is not read twice, since the slider already announces its value. For a spoken unit (“75 percent”), add aria-valuetext in the update function.
  • Focus: with the keyboard, the handle gets a solid #8b5cf6 0 0 0 3px ring (3.3:1 on the material, above the required 3:1). The browser outline is removed (outline: 0), but only because this ring replaces it.
  • Contrast: the #6b7280 titles on #e0e5ec measure 3.8:1 and the violet value #8b5cf6 3.3:1, at 12.5 px: below the 4.5:1 threshold. Switch them to #374151 (8.1:1) and #6d28d9 (5.6:1), as in the scenes. The violet fill stands out at 3.3:1 on the track; the handle only exists through its shadows (1.5:1).
  • Touch: the grab area is 24 px tall across the full width (WCAG 2.5.8 asks for at least 24 × 24 px). On a handheld screen, 32 to 44 px is more comfortable: enlarge the input and the handle together (see Customize).

Browser compatibility

CSS: appearance: none, engine-specific track and handle pseudo-elements (-webkit- for Chrome, Edge and Safari, -moz- for Firefox, in separate rules: an unknown selector would invalidate the whole rule), calc() with a CSS variable in background-size, :focus-visible. JavaScript: an input listener, style.setProperty. Measured in Chromium, Firefox and WebKit 26 (Playwright): identical rendering (10 px track, centered handle), drag, touch and keyboard working, clicks up to 11 px below the track's center registered in all three.

Chrome 86+✓ Full
Firefox 85+✓ Full
Safari 15.4+✓ Full
Edge 86+✓ Full
Mobile iOS 15.4+✓ Full (drag with a finger)
Android Chrome✓ Full

Without JavaScript, the sliders stay usable (they are native inputs) but the displayed value no longer follows and the fill stays at 50%, the default of --neu-p. The versions given are those of :focus-visible; before them, everything works except the keyboard focus ring.

The code

HTML structure to paste into your page (the CSS + JS come with Premium):

index.html — structure
<div class="neu-slider-scene">
  <label class="neu-slider-group">
    <span class="neu-slider-header">
      <span class="neu-slider-title">Volume</span>
      <span class="neu-slider-value" aria-hidden="true">75%</span>
    </span>
    <input type="range" class="neu-slider" min="0" max="100" value="75">
  </label>
  <!-- second slider: same structure -->
</div>
Unlock the full code — $9.90/mo incl. tax

The full HTML + CSS + JS of Neu Slider, ready to paste — plus all 840 effects. Or $299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Bounds and step (HTML — min, max, step, value) 0 to 100, step 1 Everything is set on the input: min="1" max="5" step="1" for five hood speeds. The JavaScript computes the ratio from min and max; the fill follows.
Displayed value (JS — out.textContent = s.value + '%') value followed by “%” Replace with your unit (' dB', ' K') or a label (['Low', 'Medium', 'High'][s.value]). Add s.setAttribute('aria-valuetext', …) with the same text.
Fill color (CSS — gradient in the background of both tracks) #8b5cf6 → #a78bfa Change it in ::-webkit-slider-runnable-track AND ::-moz-range-track, keeping the size calc(12px + (100% - 24px) * var(--neu-p, .5)). A modifier class gives one color per slider (like the amber in scene 3).
Grab size (CSS — input height: 24px, 24 px handle, margin-top: -7px) 24 px tall For 32 px: input and handle at 32 px, margin-top: -11px on the WebKit handle (half of 10 − 32), and replace the 12px / 24px of the fill formula with 16px / 32px.
Material tint (CSS — #e0e5ec, shadows #b8bec7 / #fff) light bluish gray Track, handle and the .neu-slider-scene surface share one tint: change them together.

FAQ

Because it brings for free what a div does not: mouse and finger dragging, arrows, Page keys, Home/End, value announced to screen readers, form submission. The old div version had none of it (measured: impossible to drag). The neumorphism is carried entirely by the pseudo-elements' CSS.
Listen to the native event: slider.addEventListener('input', () => update(slider.value)) to follow the drag, change for the final value. In a form, give the input a name: the value is sent with the other fields.
Neu Input carves a field for text; Neu Slider carves a track whose violet fill runs up to a raised handle you drag. For a continuous value (volume, brightness), the slider; for a precise value to type (postcode, amount), the field.