Slider
The <x-wirekit:: 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
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
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.
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:
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.
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.
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:: 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.. 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. 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.
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.
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
disabledattribute
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::with a stepper. Sliders excel at fuzzy / relative values (volume, opacity) where exact numbers don't matter.number-input> - 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.livesends again at every stop of a drag. The native range input firesinputon every step and onechangewhen it is released, and Livewire sends a.livefield 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. Usewire:model.to send only when the slider is released, or a longer debounce such aschange 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/ under components.. Override them globally:
'components' => [
'slider' => ['min' => 0, 'max' => 100, 'step' => 1, 'size' => 'md'],
],