A styled native HTML range input that looks like the Slider and works without JavaScript.
Installation
Install the dependencies
Copy the component
Copy the following files into src/lib/components/ui/native-slider.
These files use the Nova style and Lucide icons. npx @sveltery/ui@latest add writes the style and icon library from your components.json.
<script lang="ts">
import type { NativeSliderProps } from './types.js';
import { cn } from 'cn';
let { class: classProp, ...props }: NativeSliderProps = $props();
</script>
<!-- The track fill is native only in Firefox (::-moz-range-progress); filling it elsewhere would need script. -->
<input type="range" data-slot="slider" {...props} class={cn('[&::-webkit-slider-runnable-track]:bg-muted [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-1 [&::-webkit-slider-runnable-track]:w-full [&::-moz-range-track]:bg-muted [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-1 [&::-moz-range-track]:w-full [&::-moz-range-progress]:h-1 [&::-moz-range-progress]:bg-primary [&::-webkit-slider-thumb]:box-border [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:border-ring [&::-webkit-slider-thumb]:ring-ring/50 [&::-webkit-slider-thumb]:size-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:transition-[color,box-shadow] hover:[&::-webkit-slider-thumb]:ring-3 focus-visible:[&::-webkit-slider-thumb]:ring-3 focus-visible:[&::-webkit-slider-thumb]:outline-hidden active:[&::-webkit-slider-thumb]:ring-3 [&::-moz-range-thumb]:box-border [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:border-ring [&::-moz-range-thumb]:ring-ring/50 [&::-moz-range-thumb]:size-3 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border [&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:transition-[color,box-shadow] hover:[&::-moz-range-thumb]:ring-3 focus-visible:[&::-moz-range-thumb]:ring-3 focus-visible:[&::-moz-range-thumb]:outline-hidden active:[&::-moz-range-thumb]:ring-3 [&::-webkit-slider-thumb]:mt-[calc((1-3)*var(--spacing)/2)] m-0 w-full min-w-0 cursor-pointer appearance-none bg-transparent outline-none disabled:cursor-not-allowed disabled:opacity-50', classProp)} /> import type { HTMLInputAttributes } from 'svelte/elements';
export type NativeSliderProps = Omit<HTMLInputAttributes, 'type'> & { type?: 'range' }; export { default as NativeSlider } from './NativeSlider.svelte';
export type { NativeSliderProps } from './types.js'; Update the import paths
The component folders import each other with relative paths, so keep them side by side. If you place them elsewhere, change the imports to match your project setup.
Usage
<script lang="ts">
import { NativeSlider } from '#lib/components/ui/native-slider';
</script> <NativeSlider name="volume" aria-label="Volume" defaultValue={50} max={100} step={1} /> NativeSlider is a real <input type="range">, and every prop goes on it, including min, max and step. Give it its starting value with defaultValue, or the current value with value. To show the value, pair it with an <output> and update it from oninput.
The input carries the Slider's data-slot="slider". Its track and thumb are drawn from the style's Slider rules with the browser's range pseudo-elements.
When to use it
Use NativeSlider when the form must work before the page hydrates or with JavaScript off. The browser handles dragging, the arrow keys, Page Up, Page Down, Home and End, and the value submits with its form.
A native range input has one thumb and no vertical layout that works the same in every browser. Firefox fills the track left of the thumb with the style's range color; other browsers draw the track in one color, since filling it there would take JavaScript. Slider runs on the Sveltery Base Slider and has ranges, several thumbs, vertical sliders and the filled track in every browser.
Remote forms
Spread a number remote form field onto the slider with as('range'). See the example on Native Checkbox.
<Field>
<FieldLabel for="volume">Emails per week</FieldLabel>
<NativeSlider id="volume" {...f.fields.volume.as('range')} min={0} max={10} />
</Field>