Skip to main content
Copy for LLM

Slider

The <x-wirekit::slider> component is a styled wrapper around the native <input type="range">. It paints the track and thumb from WireKit's own tokens, adds size variants and an optional live value display, and keeps the native element's full accessibility.

Usage

Basic Slider

Width

The slider fills its parent width. Constrain it via the parent element:

<div class="max-w-sm">
    <x-wirekit::slider label="Volume" name="volume" :min="0" :max="100" :value="50" />
</div>

Size Variants

Three Sizes

Step Marks

Pass marks to render tick marks along the track. A plain list ([0, 25, 50, 75, 100]) draws ticks at those positions; a value-to-label map ([0 => 'Min', 100 => 'Max']) adds labels underneath.

Slider with labeled marks

Marks that carry a meaning

A third shape gives a mark a description as well as a label — for the case where the positions mean something and the label alone cannot say what:

Marks with descriptions

Hover a tick to read its meaning. This exists because the alternative is worse than it sounds: without it, a reader who wants to know what a position means has to move the slider to find out — changing the very thing they were still deciding about.

The description reaches a reader two ways, and both are needed. It becomes the tick's title, which is a pointer affordance — and there is no hover on touch, so title alone would hide the meaning from the readers most likely to be guessing at it. So it is also what the slider announces: as you move through the positions, a screen reader reads the description of the one you are on rather than the bare number. One position at a time, not every mark at once.

It also becomes what the slider announces. Where a mark has both, the description wins over the label: the label is what you see on the tick (−2), the description is what the position means (Single verdict), and the second is the useful one to hear. valueTextMap still overrides everything if you want to decouple the two entirely.

A list and a label map render their marks with no title and no description. The shape with a description is opt-in per mark, and a mark may carry a description with no label at all.

Value Tooltip

Set tooltip to float a value bubble above the thumb that follows it as the user drags — handy when there's no room for a persistent value display.

Slider with a value tooltip

Value Below the Track

The value shown by show-value sits beside the track, and the slider keeps room there for the widest value it can show, so the track does not move while it is dragged. In a form of several sliders that room differs from one slider to the next, and so do the track lengths. Set value-position="below" to put the value on its own centered line under the track instead: a longer value only changes that line, and sliders named the same way get tracks of the same length.

Values below their tracks
Seats
Budget
Tone

A visible label keeps its place beside the track, and the value centers under the track rather than under the label and the track together. The track then starts after the label, so labels of different widths still give different track lengths; the example names its sliders with a heading above each and an aria-label for that reason. With marks, the value line starts under them. value-position has no effect without show-value.

To restyle the value box yourself, select [data-wk-slider-value]. The element carrying aria-live is the announced text inside that box, not the box, so a rule aimed at it moves the text within the box and leaves the box where it is.

Custom Step

<x-wirekit::slider label="Price" name="price" :min="0" :max="1000" :step="25" :value="250" :show-value="true" />

Livewire Integration

<x-wirekit::slider> keeps its displayed value in internal state seeded from the value prop, so when binding with Livewire pass the bound property as :value alongside wire:model to seed the initial display:

<x-wirekit::slider label="Volume" wire:model.live="volume" :value="$volume" min="0" max="100" />

Without :value the slider sits at min until the user interacts; wire:model keeps it in sync afterward. This is the framework-agnostic seeding pattern WireKit's stateful controls share — it works in plain Blade forms too.

Bound to a property declared int or float, the slider is read as a number: wire:model.live goes out as wire:model.number.live. A range field gives its value as text, and Livewire would take the server's typed answer to that text for a change made on the server and write it over a value the reader set while the request was out, so a slider dragged with pauses over a slow connection would jump back. A property without a type, or a string one, gets the text.

A slider always holds a number. Bound to a property that holds null, it stands in the middle of its scale, as a native range field does, and shows that value; give the property a default to start it elsewhere.

In an Alpine component of your own, x-model on the tag binds the value the same way. See An x-model on a WireKit component.

Optimistic UI

Pass the name of the Livewire method the slider should call and the new value is shown straight away, while it goes:

<x-wirekit::slider
    name="volume"
    label="Volume"
    :value="$volume"
    optimistic="saveVolume"
/>

Load wirekit-optimistic.js alongside whichever bundle you already use — below it, in your layout:

@wirekitScripts
<script src="{{ asset('vendor/wirekit/wirekit-optimistic.js') }}" defer></script>

Try it

The demo below runs the real path: the change shows immediately, the outline says it is provisional, and the server's answer either confirms it silently or takes it back.

Optimistic slider — accepted, refused, and a slow answer

The <livewire:demos.…> wrapper above exists only on this site — it supplies the demo methods so the page can show a real round trip. The block under it is what you write.

It sends when the gesture ends, not while it runs. Dragging produces a value per frame, and sending each one would be a hundred requests for a single decision. So the value goes once, when you let go — and immediately on an arrow key, because one keypress is already a finished decision. There is no settle delay anywhere in this: a timer would make the same gesture behave differently on a fast machine than on a slow one.

A refusal puts the thumb back where the gesture started — not where you released it. Both the announced value and the control itself return, so nothing is left showing a position the server declined.

Error state

Keep the volume under 80 to stay within the venue limit.

Props

Prop Type Default Description
required bool false Marks the slider as required. Emits aria-required="true" and renders the label indicator (*). The native attribute is not used: HTML ignores required on type="range", which always has a value, so writing it there would look compliant while constraining nothing.
name string|null null Form field name
error string|null null Validation message. Rendered below the control, announced politely, and wired with aria-invalid + aria-describedby
hint string|null null Helper text below the control. An error replaces it — one message region, so the two cannot stack
help string|null null An explanation of the field, shown in a tooltip from a question mark beside the label and read as the field's description. Needs a label to stand beside. See Help beside the label
label string|null null Visible label above the control. Supplies the accessible name; without it, pass aria-label
id string|null derived Element id. Without one it is derived from name, then from the bound wire:model property (wire:model.live="volume" gives wk-slider-volume), and counted in page order when there is neither. A second slider with the same name on the page gets -2, so each label names its own slider. It is the same on every render, so a Livewire round trip keeps the input a reader is dragging. A <label for> of yours with this id names the field, and then no fallback name stands over it
min int 0 Minimum value
max int 100 Maximum value
step int|float 1 Increment step
value int|null min Initial value
size string 'md' 'sm', 'md', 'lg'
showValue bool false Show live value next to slider
valuePosition string 'end' Where the shown value sits: end, beside the track, or below, on its own centered line under the track. Only applies with showValue
marks array [] Tick marks — a list of positions ([0, 50, 100]), a position-to-label map ([0 => 'Min', 100 => 'Max']), or a map to ['label' => …, 'description' => …] for positions whose meaning the label cannot carry. See Step Marks.
valueTextMap array|null null A value => spoken-text map for aria-valuetext, decoupled from the visual ticks — show numeric ticks but announce semantic meaning ([1 => 'Low', 5 => 'High']). A caller aria-valuetext binding wins over both
tooltip bool false Float a value bubble above the thumb that follows it
disabled bool false Disabled state
optimistic string|null null Livewire method to call when the gesture ends, showing the change before the server answers
optimisticArgs array [] Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to.
scope string|null null Scoped personalization key
announceError bool|null null Render the error message in an aria-live="polite" region so a validation error that appears after render reaches a screen reader. Precedence: this prop, then the parent <x-wirekit::form :announce-errors>, then config('wirekit.a11y.announce_error'). Set false when the page runs its own live region.

Slots

Slot Description
labelAfter Markup after the label and its help question mark, in the same row and outside both, such as a badge that says a setting is not decided yet. See A Mark After the Label.

Accessibility

  • Wraps the native <input type="range"> — inherits all browser a11y features
  • Arrow keys move by step, Home/End jump to min/max, PageUp/PageDown move by larger increments
  • Live value display uses aria-live="polite" for non-intrusive AT announcements
  • Disabled state via native disabled attribute

Keyboard Interaction

Key Action
Tab Move focus to the slider thumb
ArrowLeft / ArrowDown Decrease the value by step
ArrowRight / ArrowUp Increase the value by step
PageDown Decrease by a larger amount (typically 10× step)
PageUp Increase by a larger amount
Home Jump to min
End Jump to max

Pitfalls

  • Don't use a slider for precise numeric input. Use <x-wirekit::number-input> with a stepper. Sliders excel at fuzzy / relative values (volume, opacity) where exact numbers don't matter.
  • Don't omit a numeric label. Touch users can't see the thumb's exact position — pair the slider with a live-updating value display.
  • A slider bound with wire:model.live sends again at every stop of a drag. The native range input fires input on every step and one change when it is released, and Livewire sends a .live field once its input has paused for 150 ms, its default debounce. A quick drag sends once; a drag that stops along the way sends at each stop. Use wire:model.change to send only when the slider is released, or a longer debounce such as wire:model.live.debounce.500ms.

Design Tokens

The slider is a native <input type="range"> whose track and thumb WireKit paints itself — .wk-slider::-webkit-slider-runnable-track and the Firefox equivalents are styled from the token set below. It does not inherit the browser-default accent color, and the two behave differently under a theme preset: a browser default follows the OS, and these follow --color-wk-accent.

Token Used for
--color-wk-accent Thumb fill
--color-wk-border-strong Track fill, at 3:1 against the page, since the track shows where the value stands
--color-wk-bg Thumb ring, so the thumb reads as separate from the track it sits on
--text-wk-sm Live value font size
--color-wk-text Live value text
--padding-wk-x-sm Live value pill padding
--opacity-wk-disabled Disabled visual weight

Config Defaults

The defaults live in config/wirekit.php under components.slider. Override them globally:

'components' => [
    'slider' => ['min' => 0, 'max' => 100, 'step' => 1, 'size' => 'md'],
],

Further Reading

Updated in WireKit v2.66.0 (2026-10-13)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store