Slider
This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.
Slider allows a user to select a value or range of values by moving a thumb along a track. It's ideal for adjusting settings like volume, brightness, price ranges, and other numeric inputs.
Examples
Live Preview
Basic Slider
Single value slider with label
Dual Range Slider
Select a range with two thumbs
Size Variants
Three size options: small, default, and large
Style Variants
Choose from filled and monochrome variants
With Ticks and Tooltip
Display tick marks and value tooltip
Step Increments
Control the granularity of value changes. Tick marks show the step intervals.
Custom Range
Configure min, max, and step values
Vertical Orientation
Vertical sliders for compact layouts
States
Disabled, readonly, and invalid states
With Help Text
Provide additional guidance with help text
Event Testing
Demonstrates the dual-dispatch event pattern: both CustomEvents (via @input/@change) and native events work simultaneously. Drag the slider to see real-time events.
Event Log (last 10 events):
Dual Range Events
Event handling with dual range sliders
Range Event Log (last 10 events):
CSS Shadow Parts Customization
Use CSS Shadow Parts to customize the component's appearance. These examples show customized track, thumb, and label styling.
External Label Support
The Slider component supports external labels with helper text, required fields, and validation states.
Label Positioning
Control label position with label-position: 'top' (default), 'start', 'end', or 'bottom'.
View Vue Code
<template>
<section>
<div class="mbe4">
<h2>Basic Slider</h2>
<p class="mbs2 mbe3">Single value slider with label</p>
</div>
<div class="mbe4">
<VueSlider
label="Volume"
:value="75"
/>
</div>
<div class="mbe4">
<h2>Dual Range Slider</h2>
<p class="mbs2 mbe3">Select a range with two thumbs</p>
</div>
<div class="mbe4">
<VueSlider
label="Price Range ($0 - $1000)"
dual
:min="0"
:max="1000"
:value="[200, 800]"
/>
</div>
<div class="mbe4">
<h2>Size Variants</h2>
<p class="mbs2 mbe3">Three size options: small, default, and large</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<VueSlider
label="Small"
size="small"
:value="30"
/>
<VueSlider
label="Default"
size="default"
:value="50"
/>
<VueSlider
label="Large"
size="large"
:value="70"
/>
</div>
<div class="mbe4">
<h2>Style Variants</h2>
<p class="mbs2 mbe3">Choose from filled and monochrome variants</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<fieldset style="border: 1px solid var(--ag-border); padding: 1rem; border-radius: var(--ag-radius-md);">
<legend style="padding: 0 0.5rem; font-weight: 600;">Default (Primary Color)</legend>
<VueSlider
label="Standard"
:value="50"
/>
</fieldset>
<fieldset style="border: 1px solid var(--ag-border); padding: 1rem; border-radius: var(--ag-radius-md);">
<legend style="padding: 0 0.5rem; font-weight: 600;">Filled Variant</legend>
<VueSlider
label="Filled Thumb"
filled
:value="60"
/>
</fieldset>
<fieldset style="border: 1px solid var(--ag-border); padding: 1rem; border-radius: var(--ag-radius-md);">
<legend style="padding: 0 0.5rem; font-weight: 600;">Monochrome (Adapts to Dark Mode)</legend>
<VueSlider
label="Monochrome"
monochrome
:value="70"
/>
</fieldset>
<fieldset style="border: 1px solid var(--ag-border); padding: 1rem; border-radius: var(--ag-radius-md);">
<legend style="padding: 0 0.5rem; font-weight: 600;">Monochrome Filled</legend>
<VueSlider
label="Monochrome Filled"
monochrome
filled
:value="80"
/>
</fieldset>
</div>
<div class="mbe4">
<h2>With Ticks and Tooltip</h2>
<p class="mbs2 mbe3">Display tick marks and value tooltip</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<VueSlider
label="With Tick Marks"
show-ticks
:tick-step="25"
:value="50"
/>
<VueSlider
label="With Tooltip"
show-tooltip
:value="65"
/>
<VueSlider
label="Ticks + Tooltip"
show-ticks
show-tooltip
:tick-step="20"
:value="40"
/>
</div>
<div class="mbe4">
<h2>Step Increments</h2>
<p class="mbs2 mbe3">Control the granularity of value changes. Tick marks show the step intervals.</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<VueSlider
label="Step 5"
:step="5"
:value="50"
show-ticks
:tick-step="5"
/>
<VueSlider
label="Step 10"
:step="10"
:value="50"
show-ticks
:tick-step="10"
/>
<VueSlider
label="Step 25 (Coarse)"
:step="25"
:value="50"
show-ticks
:tick-step="25"
/>
</div>
<div class="mbe4">
<h2>Custom Range</h2>
<p class="mbs2 mbe3">Configure min, max, and step values</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<VueSlider
label="Temperature (°C)"
:min="-20"
:max="40"
:step="5"
:value="20"
/>
<VueSlider
label="Percentage"
:min="0"
:max="100"
:step="1"
:value="75"
help-text="Adjust from 0% to 100%"
/>
</div>
<div class="mbe4">
<h2>Vertical Orientation</h2>
<p class="mbs2 mbe3">Vertical sliders for compact layouts</p>
</div>
<div
class="mbe4"
style="display: flex; gap: 2rem; align-items: flex-end;"
>
<VueSlider
label="Volume"
vertical
:value="75"
/>
<VueSlider
label="Bass"
vertical
size="small"
:value="50"
/>
<VueSlider
label="Treble"
vertical
size="large"
:value="60"
/>
<VueSlider
label="Balance"
vertical
dual
:value="[25, 75]"
/>
</div>
<div class="mbe4">
<h2>States</h2>
<p class="mbs2 mbe3">Disabled, readonly, and invalid states</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<VueSlider
label="Disabled"
disabled
:value="50"
help-text="This slider is disabled"
/>
<VueSlider
label="Readonly"
readonly
:value="65"
help-text="This slider is readonly"
/>
<VueSlider
label="Invalid"
invalid
error-message="Value must be at least 50"
:value="30"
/>
</div>
<div class="mbe4">
<h2>With Help Text</h2>
<p class="mbs2 mbe3">Provide additional guidance with help text</p>
</div>
<div class="mbe4">
<VueSlider
label="Brightness"
help-text="Adjust screen brightness level (affects battery life)"
:value="75"
/>
</div>
<div class="mbe4">
<h2>Event Testing</h2>
<p class="mbs2 mbe3">
Demonstrates the dual-dispatch event pattern: both CustomEvents (via @input/@change)
and native events work simultaneously. Drag the slider to see real-time events.
</p>
</div>
<div class="mbe4">
<VueSlider
label="Interactive Slider"
:value="eventValue"
@input="handleInput"
@change="handleChange"
@focus="handleFocus"
@blur="handleBlur"
/>
<div
class="event-log"
style="margin-top: 1.5rem; padding: 1rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md); border: 1px solid var(--ag-border);"
>
<h3 style="margin-top: 0; margin-bottom: 0.75rem; font-size: 0.95rem;">Event Log (last 10 events):</h3>
<div
v-if="events.length === 0"
style="color: var(--ag-text-muted); font-size: 0.875rem; font-style: italic;"
>
Interact with the slider to see events...
</div>
<div
v-for="(event, i) in events"
:key="i"
style="font-family: monospace; font-size: 0.875rem; padding: 0.25rem 0; color: var(--ag-text-primary);"
>
{{ event }}
</div>
</div>
<div style="margin-top: 1rem; font-size: 0.875rem; color: var(--ag-text-secondary);">
Current value: <strong>{{ eventValue }}</strong>
</div>
</div>
<div class="mbe4">
<h2>Dual Range Events</h2>
<p class="mbs2 mbe3">Event handling with dual range sliders</p>
</div>
<div class="mbe4">
<VueSlider
label="Price Range Filter"
dual
:min="0"
:max="1000"
:step="10"
:value="rangeValue"
@input="handleRangeInput"
@change="handleRangeChange"
/>
<div
class="event-log"
style="margin-top: 1.5rem; padding: 1rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md); border: 1px solid var(--ag-border);"
>
<h3 style="margin-top: 0; margin-bottom: 0.75rem; font-size: 0.95rem;">Range Event Log (last 10 events):</h3>
<div
v-if="rangeEvents.length === 0"
style="color: var(--ag-text-muted); font-size: 0.875rem; font-style: italic;"
>
Drag the range handles to see events...
</div>
<div
v-for="(event, i) in rangeEvents"
:key="i"
style="font-family: monospace; font-size: 0.875rem; padding: 0.25rem 0; color: var(--ag-text-primary);"
>
{{ event }}
</div>
</div>
<div style="margin-top: 1rem; font-size: 0.875rem; color: var(--ag-text-secondary);">
Current range: <strong>${{ rangeValue[0] }} - ${{ rangeValue[1] }}</strong>
</div>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p class="mbs2 mbe3">
Use CSS Shadow Parts to customize the component's appearance.
These examples show customized track, thumb, and label styling.
</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<VueSlider
label="Gradient Track"
:value="60"
class="custom-slider-1"
/>
<VueSlider
label="Large Thumb with Shadow"
:value="70"
class="custom-slider-2"
/>
<VueSlider
label="Fully Customized"
:value="50"
class="custom-slider-3"
help-text="Custom colors, sizing, and typography"
/>
</div>
<div class="mbe4">
<h2>External Label Support</h2>
<p class="mbs2 mbe3">
The Slider component supports external labels with helper text, required fields, and validation states.
</p>
</div>
<div
class="mbe4"
style="max-width: 600px;"
>
<div class="mbe3">
<VueSlider
label="Volume Level"
:min="0"
:max="100"
:value="75"
/>
</div>
<div class="mbe3">
<VueSlider
label="Brightness"
help-text="Adjust screen brightness level (affects battery life)"
:min="0"
:max="100"
:value="50"
/>
</div>
<div class="mbe3">
<VueSlider
label="Temperature"
:required="true"
help-text="This field is required"
:min="-20"
:max="40"
:value="20"
/>
</div>
<div class="mbe3">
<VueSlider
label="Price Range"
:required="true"
:invalid="true"
error-message="Please select a valid price range"
:min="0"
:max="1000"
:value="50"
/>
</div>
</div>
<div class="mbe4">
<h2>Label Positioning</h2>
<p class="mbs2 mbe3">
Control label position with <code>label-position</code>: 'top' (default), 'start', 'end', or 'bottom'.
</p>
</div>
<div
class="mbe4"
style="max-width: 600px;"
>
<div class="mbe3">
<VueSlider
label="Top Label (Default)"
label-position="top"
:min="0"
:max="100"
:value="50"
/>
</div>
<div class="mbe3">
<VueSlider
label="Start Position"
label-position="start"
:min="0"
:max="100"
:value="60"
/>
</div>
<div class="mbe3">
<VueSlider
label="End Position"
label-position="end"
:min="0"
:max="100"
:value="70"
/>
</div>
<div class="mbe3">
<VueSlider
label="Bottom Position"
label-position="bottom"
help-text="Bottom label position for alternative layouts"
:min="0"
:max="100"
:value="40"
/>
</div>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { VueSlider } from "agnosticui-core/slider/vue";
export default defineComponent({
name: "SliderExamples",
components: {
VueSlider,
},
setup() {
const eventValue = ref(50);
const events = ref<string[]>([]);
const rangeValue = ref<[number, number]>([200, 800]);
const rangeEvents = ref<string[]>([]);
const addEvent = (event: string) => {
events.value = [
...events.value,
`${new Date().toLocaleTimeString()}: ${event}`,
].slice(-10);
};
const addRangeEvent = (event: string) => {
rangeEvents.value = [
...rangeEvents.value,
`${new Date().toLocaleTimeString()}: ${event}`,
].slice(-10);
};
const handleInput = (detail: { value: number | [number, number] }) => {
eventValue.value = detail.value as number;
addEvent(`📝 input: value=${detail.value}`);
};
const handleChange = (detail: { value: number | [number, number] }) => {
addEvent(`✅ change: value=${detail.value}`);
};
const handleFocus = () => {
addEvent("🎯 focus");
};
const handleBlur = () => {
addEvent("👋 blur");
};
const handleRangeInput = (detail: { value: number | [number, number] }) => {
rangeValue.value = detail.value as [number, number];
const [min, max] = detail.value as [number, number];
addRangeEvent(`📝 input: range=[$${min}, $${max}]`);
};
const handleRangeChange = (detail: {
value: number | [number, number];
}) => {
const [min, max] = detail.value as [number, number];
addRangeEvent(`✅ change: range=[$${min}, $${max}]`);
};
return {
eventValue,
events,
rangeValue,
rangeEvents,
handleInput,
handleChange,
handleFocus,
handleBlur,
handleRangeInput,
handleRangeChange,
};
},
});
</script>
<style>
/* CSS Shadow Parts customization examples */
/* Custom slider 1: Gradient track */
.custom-slider-1::part(ag-slider-track) {
height: 10px;
background: var(--ag-background-tertiary);
}
.custom-slider-1::part(ag-slider-progress) {
background: linear-gradient(90deg, var(--ag-green-500), var(--ag-blue-500));
}
/* Custom slider 2: Large thumb with enhanced shadow */
.custom-slider-2::part(ag-slider-thumb) {
width: 28px;
height: 28px;
border: 4px solid var(--ag-primary);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.custom-slider-2::part(ag-slider-track) {
height: 8px;
}
/* Custom slider 3: Fully customized */
.custom-slider-3::part(ag-slider-label) {
font-weight: 700;
font-size: 1.1rem;
}
.custom-slider-3::part(ag-slider-track) {
height: 12px;
background: var(--ag-background-tertiary);
border-radius: var(--ag-radius-lg);
border: 2px solid var(--ag-border);
}
.custom-slider-3::part(ag-slider-progress) {
background: linear-gradient(
135deg,
var(--ag-purple-500) 0%,
var(--ag-blue-500) 100%
);
border-radius: var(--ag-radius-lg);
}
.custom-slider-3::part(ag-slider-thumb) {
width: 32px;
height: 32px;
background: var(--ag-white);
border: 4px solid var(--ag-purple-500);
box-shadow: 0 4px 16px rgba(137, 87, 229, 0.4);
}
.custom-slider-3::part(ag-slider-help-text) {
color: var(--ag-purple-500);
font-style: italic;
}
</style>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/slider';
export class SliderLitExamples extends LitElement {
static properties = {
eventValue: { type: Number },
events: { type: Array },
rangeValue: { type: Array },
rangeEvents: { type: Array },
};
constructor() {
super();
this.eventValue = 50;
this.events = [];
this.rangeValue = [200, 800];
this.rangeEvents = [];
}
// CRITICAL: Must include createRenderRoot() to use light DOM
// Without this, global CSS utility classes won't work!
createRenderRoot() {
return this;
}
addEvent(event) {
this.events = [
...this.events,
`${new Date().toLocaleTimeString()}: ${event}`,
].slice(-10);
}
addRangeEvent(event) {
this.rangeEvents = [
...this.rangeEvents,
`${new Date().toLocaleTimeString()}: ${event}`,
].slice(-10);
}
handleInput(e) {
this.eventValue = e.detail.value;
this.addEvent(`📝 input: value=${e.detail.value}`);
}
handleChange(e) {
this.addEvent(`✅ change: value=${e.detail.value}`);
}
handleFocus() {
this.addEvent('🎯 focus');
}
handleBlur() {
this.addEvent('👋 blur');
}
handleRangeInput(e) {
this.rangeValue = e.detail.value;
const [min, max] = e.detail.value;
this.addRangeEvent(`📝 input: range=[$${min}, $${max}]`);
}
handleRangeChange(e) {
const [min, max] = e.detail.value;
this.addRangeEvent(`✅ change: range=[$${min}, $${max}]`);
}
render() {
return html`
<section>
<div class="mbe4">
<h2>Basic Slider</h2>
<p class="mbs2 mbe3">Single value slider with label</p>
</div>
<div class="mbe4">
<ag-slider
label="Volume"
value="75"
></ag-slider>
</div>
<div class="mbe4">
<h2>Dual Range Slider</h2>
<p class="mbs2 mbe3">Select a range with two thumbs</p>
</div>
<div class="mbe4">
<ag-slider
label="Price Range ($0 - $1000)"
dual
min="0"
max="1000"
.value="${[200, 800]}"
></ag-slider>
</div>
<div class="mbe4">
<h2>Size Variants</h2>
<p class="mbs2 mbe3">Three size options: small, default, and large</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<ag-slider
label="Small"
size="small"
value="30"
></ag-slider>
<ag-slider
label="Default"
size="default"
value="50"
></ag-slider>
<ag-slider
label="Large"
size="large"
value="70"
></ag-slider>
</div>
<div class="mbe4">
<h2>Style Variants</h2>
<p class="mbs2 mbe3">Choose from filled and monochrome variants</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<fieldset style="border: 1px solid var(--ag-border); padding: 1rem; border-radius: var(--ag-radius-md);">
<legend style="padding: 0 0.5rem; font-weight: 600;">Default (Primary Color)</legend>
<ag-slider
label="Standard"
value="50"
></ag-slider>
</fieldset>
<fieldset style="border: 1px solid var(--ag-border); padding: 1rem; border-radius: var(--ag-radius-md);">
<legend style="padding: 0 0.5rem; font-weight: 600;">Filled Variant</legend>
<ag-slider
label="Filled Thumb"
filled
value="60"
></ag-slider>
</fieldset>
<fieldset style="border: 1px solid var(--ag-border); padding: 1rem; border-radius: var(--ag-radius-md);">
<legend style="padding: 0 0.5rem; font-weight: 600;">Monochrome (Adapts to Dark Mode)</legend>
<ag-slider
label="Monochrome"
monochrome
value="70"
></ag-slider>
</fieldset>
<fieldset style="border: 1px solid var(--ag-border); padding: 1rem; border-radius: var(--ag-radius-md);">
<legend style="padding: 0 0.5rem; font-weight: 600;">Monochrome Filled</legend>
<ag-slider
label="Monochrome Filled"
monochrome
filled
value="80"
></ag-slider>
</fieldset>
</div>
<div class="mbe4">
<h2>With Ticks and Tooltip</h2>
<p class="mbs2 mbe3">Display tick marks and value tooltip</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<ag-slider
label="With Tick Marks"
show-ticks
tick-step="25"
value="50"
></ag-slider>
<ag-slider
label="With Tooltip"
show-tooltip
value="65"
></ag-slider>
<ag-slider
label="Ticks + Tooltip"
show-ticks
show-tooltip
tick-step="20"
value="40"
></ag-slider>
</div>
<div class="mbe4">
<h2>Step Increments</h2>
<p class="mbs2 mbe3">Control the granularity of value changes. Tick marks show the step intervals.</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<ag-slider
label="Step 5"
step="5"
value="50"
show-ticks
tick-step="5"
></ag-slider>
<ag-slider
label="Step 10"
step="10"
value="50"
show-ticks
tick-step="10"
></ag-slider>
<ag-slider
label="Step 25 (Coarse)"
step="25"
value="50"
show-ticks
tick-step="25"
></ag-slider>
</div>
<div class="mbe4">
<h2>Custom Range</h2>
<p class="mbs2 mbe3">Configure min, max, and step values</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<ag-slider
label="Temperature (°C)"
min="-20"
max="40"
step="5"
value="20"
></ag-slider>
<ag-slider
label="Percentage"
min="0"
max="100"
step="1"
value="75"
help-text="Adjust from 0% to 100%"
></ag-slider>
</div>
<div class="mbe4">
<h2>Vertical Orientation</h2>
<p class="mbs2 mbe3">Vertical sliders for compact layouts</p>
</div>
<div
class="mbe4"
style="display: flex; gap: 2rem; align-items: flex-end;"
>
<ag-slider
label="Volume"
vertical
value="75"
></ag-slider>
<ag-slider
label="Bass"
vertical
size="small"
value="50"
></ag-slider>
<ag-slider
label="Treble"
vertical
size="large"
value="60"
></ag-slider>
<ag-slider
label="Balance"
vertical
dual
.value="${[25, 75]}"
></ag-slider>
</div>
<div class="mbe4">
<h2>States</h2>
<p class="mbs2 mbe3">Disabled, readonly, and invalid states</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<ag-slider
label="Disabled"
disabled
value="50"
help-text="This slider is disabled"
></ag-slider>
<ag-slider
label="Readonly"
readonly
value="65"
help-text="This slider is readonly"
></ag-slider>
<ag-slider
label="Invalid"
invalid
error-message="Value must be at least 50"
value="30"
></ag-slider>
</div>
<div class="mbe4">
<h2>With Help Text</h2>
<p class="mbs2 mbe3">Provide additional guidance with help text</p>
</div>
<div class="mbe4">
<ag-slider
label="Brightness"
help-text="Adjust screen brightness level (affects battery life)"
value="75"
></ag-slider>
</div>
<div class="mbe4">
<h2>Event Testing</h2>
<p class="mbs2 mbe3">
Demonstrates the dual-dispatch event pattern: both CustomEvents (via @input/@change)
and native events work simultaneously. Drag the slider to see real-time events.
</p>
</div>
<div class="mbe4">
<ag-slider
label="Interactive Slider"
.value="${this.eventValue}"
@input="${this.handleInput}"
@change="${this.handleChange}"
@focus="${this.handleFocus}"
@blur="${this.handleBlur}"
></ag-slider>
<div
class="event-log"
style="margin-top: 1.5rem; padding: 1rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md); border: 1px solid var(--ag-border);"
>
<h3 style="margin-top: 0; margin-bottom: 0.75rem; font-size: 0.95rem;">Event Log (last 10 events):</h3>
${this.events.length === 0 ? html`
<div style="color: var(--ag-text-muted); font-size: 0.875rem; font-style: italic;">
Interact with the slider to see events...
</div>
` : this.events.map(event => html`
<div style="font-family: monospace; font-size: 0.875rem; padding: 0.25rem 0; color: var(--ag-text-primary);">
${event}
</div>
`)}
</div>
<div style="margin-top: 1rem; font-size: 0.875rem; color: var(--ag-text-secondary);">
Current value: <strong>${this.eventValue}</strong>
</div>
</div>
<div class="mbe4">
<h2>Dual Range Events</h2>
<p class="mbs2 mbe3">Event handling with dual range sliders</p>
</div>
<div class="mbe4">
<ag-slider
label="Price Range Filter"
dual
min="0"
max="1000"
step="10"
.value="${this.rangeValue}"
@input="${this.handleRangeInput}"
@change="${this.handleRangeChange}"
></ag-slider>
<div
class="event-log"
style="margin-top: 1.5rem; padding: 1rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md); border: 1px solid var(--ag-border);"
>
<h3 style="margin-top: 0; margin-bottom: 0.75rem; font-size: 0.95rem;">Range Event Log (last 10 events):</h3>
${this.rangeEvents.length === 0 ? html`
<div style="color: var(--ag-text-muted); font-size: 0.875rem; font-style: italic;">
Drag the range handles to see events...
</div>
` : this.rangeEvents.map(event => html`
<div style="font-family: monospace; font-size: 0.875rem; padding: 0.25rem 0; color: var(--ag-text-primary);">
${event}
</div>
`)}
</div>
<div style="margin-top: 1rem; font-size: 0.875rem; color: var(--ag-text-secondary);">
Current range: <strong>$${this.rangeValue[0]} - $${this.rangeValue[1]}</strong>
</div>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p class="mbs2 mbe3">
Use CSS Shadow Parts to customize the component's appearance.
These examples show customized track, thumb, and label styling.
</p>
</div>
<div
class="mbe4"
style="display: flex; flex-direction: column; gap: 2rem;"
>
<ag-slider
label="Gradient Track"
value="60"
class="custom-slider-1"
></ag-slider>
<ag-slider
label="Large Thumb with Shadow"
value="70"
class="custom-slider-2"
></ag-slider>
<ag-slider
label="Fully Customized"
value="50"
class="custom-slider-3"
help-text="Custom colors, sizing, and typography"
></ag-slider>
</div>
<div class="mbe4">
<h2>External Label Support</h2>
<p class="mbs2 mbe3">
The Slider component supports external labels with helper text, required fields, and validation states.
</p>
</div>
<div
class="mbe4"
style="max-width: 600px;"
>
<div class="mbe3">
<ag-slider
label="Volume Level"
min="0"
max="100"
value="75"
></ag-slider>
</div>
<div class="mbe3">
<ag-slider
label="Brightness"
help-text="Adjust screen brightness level (affects battery life)"
min="0"
max="100"
value="50"
></ag-slider>
</div>
<div class="mbe3">
<ag-slider
label="Temperature"
required
help-text="This field is required"
min="-20"
max="40"
value="20"
></ag-slider>
</div>
<div class="mbe3">
<ag-slider
label="Price Range"
required
invalid
error-message="Please select a valid price range"
min="0"
max="1000"
value="50"
></ag-slider>
</div>
</div>
<div class="mbe4">
<h2>Label Positioning</h2>
<p class="mbs2 mbe3">
Control label position with <code>label-position</code>: 'top' (default), 'start', 'end', or 'bottom'.
</p>
</div>
<div
class="mbe4"
style="max-width: 600px;"
>
<div class="mbe3">
<ag-slider
label="Top Label (Default)"
label-position="top"
min="0"
max="100"
value="50"
></ag-slider>
</div>
<div class="mbe3">
<ag-slider
label="Start Position"
label-position="start"
min="0"
max="100"
value="60"
></ag-slider>
</div>
<div class="mbe3">
<ag-slider
label="End Position"
label-position="end"
min="0"
max="100"
value="70"
></ag-slider>
</div>
<div class="mbe3">
<ag-slider
label="Bottom Position"
label-position="bottom"
help-text="Bottom label position for alternative layouts"
min="0"
max="100"
value="40"
></ag-slider>
</div>
</div>
</section>
`;
}
}
// Register the custom element (at the bottom, NOT with decorator)
customElements.define('slider-lit-examples', SliderLitExamples);
Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.
View React Code
import { useState } from "react";
import { ReactSlider } from "agnosticui-core/slider/react";
export default function SliderReactExamples() {
const [eventValue, setEventValue] = useState(50);
const [events, setEvents] = useState([]);
const [rangeValue, setRangeValue] = useState([200, 800]);
const [rangeEvents, setRangeEvents] = useState([]);
const addEvent = (event) => {
setEvents((prev) => [
...prev,
`${new Date().toLocaleTimeString()}: ${event}`,
].slice(-10));
};
const addRangeEvent = (event) => {
setRangeEvents((prev) => [
...prev,
`${new Date().toLocaleTimeString()}: ${event}`,
].slice(-10));
};
const handleInput = (detail) => {
setEventValue(detail.value);
addEvent(`📝 input: value=${detail.value}`);
};
const handleChange = (detail) => {
addEvent(`✅ change: value=${detail.value}`);
};
const handleFocus = () => {
addEvent('🎯 focus');
};
const handleBlur = () => {
addEvent('👋 blur');
};
const handleRangeInput = (detail) => {
setRangeValue(detail.value);
const [min, max] = detail.value;
addRangeEvent(`📝 input: range=[$${min}, $${max}]`);
};
const handleRangeChange = (detail) => {
const [min, max] = detail.value;
addRangeEvent(`✅ change: range=[$${min}, $${max}]`);
};
return (
<section>
<div className="mbe4">
<h2>Basic Slider</h2>
<p className="mbs2 mbe3">Single value slider with label</p>
</div>
<div className="mbe4">
<ReactSlider
label="Volume"
value={75}
/>
</div>
<div className="mbe4">
<h2>Dual Range Slider</h2>
<p className="mbs2 mbe3">Select a range with two thumbs</p>
</div>
<div className="mbe4">
<ReactSlider
label="Price Range ($0 - $1000)"
dual={true}
min={0}
max={1000}
value={[200, 800]}
/>
</div>
<div className="mbe4">
<h2>Size Variants</h2>
<p className="mbs2 mbe3">Three size options: small, default, and large</p>
</div>
<div
className="mbe4"
style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}
>
<ReactSlider
label="Small"
size="small"
value={30}
/>
<ReactSlider
label="Default"
size="default"
value={50}
/>
<ReactSlider
label="Large"
size="large"
value={70}
/>
</div>
<div className="mbe4">
<h2>Style Variants</h2>
<p className="mbs2 mbe3">Choose from filled and monochrome variants</p>
</div>
<div
className="mbe4"
style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}
>
<fieldset style={{ border: '1px solid var(--ag-border)', padding: '1rem', borderRadius: 'var(--ag-radius-md)' }}>
<legend style={{ padding: '0 0.5rem', fontWeight: 600 }}>Default (Primary Color)</legend>
<ReactSlider
label="Standard"
value={50}
/>
</fieldset>
<fieldset style={{ border: '1px solid var(--ag-border)', padding: '1rem', borderRadius: 'var(--ag-radius-md)' }}>
<legend style={{ padding: '0 0.5rem', fontWeight: 600 }}>Filled Variant</legend>
<ReactSlider
label="Filled Thumb"
filled={true}
value={60}
/>
</fieldset>
<fieldset style={{ border: '1px solid var(--ag-border)', padding: '1rem', borderRadius: 'var(--ag-radius-md)' }}>
<legend style={{ padding: '0 0.5rem', fontWeight: 600 }}>Monochrome (Adapts to Dark Mode)</legend>
<ReactSlider
label="Monochrome"
monochrome={true}
value={70}
/>
</fieldset>
<fieldset style={{ border: '1px solid var(--ag-border)', padding: '1rem', borderRadius: 'var(--ag-radius-md)' }}>
<legend style={{ padding: '0 0.5rem', fontWeight: 600 }}>Monochrome Filled</legend>
<ReactSlider
label="Monochrome Filled"
monochrome={true}
filled={true}
value={80}
/>
</fieldset>
</div>
<div className="mbe4">
<h2>With Ticks and Tooltip</h2>
<p className="mbs2 mbe3">Display tick marks and value tooltip</p>
</div>
<div
className="mbe4"
style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}
>
<ReactSlider
label="With Tick Marks"
showTicks={true}
tickStep={25}
value={50}
/>
<ReactSlider
label="With Tooltip"
showTooltip={true}
value={65}
/>
<ReactSlider
label="Ticks + Tooltip"
showTicks={true}
showTooltip={true}
tickStep={20}
value={40}
/>
</div>
<div className="mbe4">
<h2>Step Increments</h2>
<p className="mbs2 mbe3">Control the granularity of value changes. Tick marks show the step intervals.</p>
</div>
<div
className="mbe4"
style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}
>
<ReactSlider
label="Step 5"
step={5}
value={50}
showTicks={true}
tickStep={5}
/>
<ReactSlider
label="Step 10"
step={10}
value={50}
showTicks={true}
tickStep={10}
/>
<ReactSlider
label="Step 25 (Coarse)"
step={25}
value={50}
showTicks={true}
tickStep={25}
/>
</div>
<div className="mbe4">
<h2>Custom Range</h2>
<p className="mbs2 mbe3">Configure min, max, and step values</p>
</div>
<div
className="mbe4"
style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}
>
<ReactSlider
label="Temperature (°C)"
min={-20}
max={40}
step={5}
value={20}
/>
<ReactSlider
label="Percentage"
min={0}
max={100}
step={1}
value={75}
helpText="Adjust from 0% to 100%"
/>
</div>
<div className="mbe4">
<h2>Vertical Orientation</h2>
<p className="mbs2 mbe3">Vertical sliders for compact layouts</p>
</div>
<div
className="mbe4"
style={{ display: 'flex', gap: '2rem', alignItems: 'flex-end' }}
>
<ReactSlider
label="Volume"
vertical={true}
value={75}
/>
<ReactSlider
label="Bass"
vertical={true}
size="small"
value={50}
/>
<ReactSlider
label="Treble"
vertical={true}
size="large"
value={60}
/>
<ReactSlider
label="Balance"
vertical={true}
dual={true}
value={[25, 75]}
/>
</div>
<div className="mbe4">
<h2>States</h2>
<p className="mbs2 mbe3">Disabled, readonly, and invalid states</p>
</div>
<div
className="mbe4"
style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}
>
<ReactSlider
label="Disabled"
disabled={true}
value={50}
helpText="This slider is disabled"
/>
<ReactSlider
label="Readonly"
readonly={true}
value={65}
helpText="This slider is readonly"
/>
<ReactSlider
label="Invalid"
invalid={true}
errorMessage="Value must be at least 50"
value={30}
/>
</div>
<div className="mbe4">
<h2>With Help Text</h2>
<p className="mbs2 mbe3">Provide additional guidance with help text</p>
</div>
<div className="mbe4">
<ReactSlider
label="Brightness"
helpText="Adjust screen brightness level (affects battery life)"
value={75}
/>
</div>
<div className="mbe4">
<h2>Event Testing</h2>
<p className="mbs2 mbe3">
Demonstrates the dual-dispatch event pattern: both CustomEvents (via @input/@change)
and native events work simultaneously. Drag the slider to see real-time events.
</p>
</div>
<div className="mbe4">
<ReactSlider
label="Interactive Slider"
value={eventValue}
onInput={handleInput}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
/>
<div
className="event-log"
style={{ marginTop: '1.5rem', padding: '1rem', background: 'var(--ag-background-secondary)', borderRadius: 'var(--ag-radius-md)', border: '1px solid var(--ag-border)' }}
>
<h3 style={{ marginTop: 0, marginBottom: '0.75rem', fontSize: '0.95rem' }}>Event Log (last 10 events):</h3>
{events.length === 0 ? (
<div style={{ color: 'var(--ag-text-muted)', fontSize: '0.875rem', fontStyle: 'italic' }}>
Interact with the slider to see events...
</div>
) : (
events.map((event, i) => (
<div
key={i}
style={{ fontFamily: 'monospace', fontSize: '0.875rem', padding: '0.25rem 0', color: 'var(--ag-text-primary)' }}
>
{event}
</div>
))
)}
</div>
<div style={{ marginTop: '1rem', fontSize: '0.875rem', color: 'var(--ag-text-secondary)' }}>
Current value: <strong>{eventValue}</strong>
</div>
</div>
<div className="mbe4">
<h2>Dual Range Events</h2>
<p className="mbs2 mbe3">Event handling with dual range sliders</p>
</div>
<div className="mbe4">
<ReactSlider
label="Price Range Filter"
dual={true}
min={0}
max={1000}
step={10}
value={rangeValue}
onInput={handleRangeInput}
onChange={handleRangeChange}
/>
<div
className="event-log"
style={{ marginTop: '1.5rem', padding: '1rem', background: 'var(--ag-background-secondary)', borderRadius: 'var(--ag-radius-md)', border: '1px solid var(--ag-border)' }}
>
<h3 style={{ marginTop: 0, marginBottom: '0.75rem', fontSize: '0.95rem' }}>Range Event Log (last 10 events):</h3>
{rangeEvents.length === 0 ? (
<div style={{ color: 'var(--ag-text-muted)', fontSize: '0.875rem', fontStyle: 'italic' }}>
Drag the range handles to see events...
</div>
) : (
rangeEvents.map((event, i) => (
<div
key={i}
style={{ fontFamily: 'monospace', fontSize: '0.875rem', padding: '0.25rem 0', color: 'var(--ag-text-primary)' }}
>
{event}
</div>
))
)}
</div>
<div style={{ marginTop: '1rem', fontSize: '0.875rem', color: 'var(--ag-text-secondary)' }}>
Current range: <strong>${rangeValue[0]} - ${rangeValue[1]}</strong>
</div>
</div>
<div className="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p className="mbs2 mbe3">
Use CSS Shadow Parts to customize the component's appearance.
These examples show customized track, thumb, and label styling.
</p>
</div>
<div
className="mbe4"
style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}
>
<ReactSlider
label="Gradient Track"
value={60}
className="custom-slider-1"
/>
<ReactSlider
label="Large Thumb with Shadow"
value={70}
className="custom-slider-2"
/>
<ReactSlider
label="Fully Customized"
value={50}
className="custom-slider-3"
helpText="Custom colors, sizing, and typography"
/>
</div>
<div className="mbe4">
<h2>External Label Support</h2>
<p className="mbs2 mbe3">
The Slider component supports external labels with helper text, required fields, and validation states.
</p>
</div>
<div
className="mbe4"
style={{ maxWidth: '600px' }}
>
<div className="mbe3">
<ReactSlider
label="Volume Level"
min={0}
max={100}
value={75}
/>
</div>
<div className="mbe3">
<ReactSlider
label="Brightness"
helpText="Adjust screen brightness level (affects battery life)"
min={0}
max={100}
value={50}
/>
</div>
<div className="mbe3">
<ReactSlider
label="Temperature"
required={true}
helpText="This field is required"
min={-20}
max={40}
value={20}
/>
</div>
<div className="mbe3">
<ReactSlider
label="Price Range"
required={true}
invalid={true}
errorMessage="Please select a valid price range"
min={0}
max={1000}
value={50}
/>
</div>
</div>
<div className="mbe4">
<h2>Label Positioning</h2>
<p className="mbs2 mbe3">
Control label position with <code>label-position</code>: 'top' (default), 'start', 'end', or 'bottom'.
</p>
</div>
<div
className="mbe4"
style={{ maxWidth: '600px' }}
>
<div className="mbe3">
<ReactSlider
label="Top Label (Default)"
labelPosition="top"
min={0}
max={100}
value={50}
/>
</div>
<div className="mbe3">
<ReactSlider
label="Start Position"
labelPosition="start"
min={0}
max={100}
value={60}
/>
</div>
<div className="mbe3">
<ReactSlider
label="End Position"
labelPosition="end"
min={0}
max={100}
value={70}
/>
</div>
<div className="mbe3">
<ReactSlider
label="Bottom Position"
labelPosition="bottom"
helpText="Bottom label position for alternative layouts"
min={0}
max={100}
value={40}
/>
</div>
</div>
</section>
);
}
Usage
TIP
The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add SliderThe CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.
Vue
<template>
<section>
<!-- Basic slider -->
<VueSlider label="Volume" :min="0" :max="100" :value="75" />
<!-- Dual range slider -->
<VueSlider
label="Price Range"
dual
:min="0"
:max="1000"
:value="[200, 800]"
/>
<!-- With ticks and tooltip -->
<VueSlider
label="Brightness"
:min="0"
:max="100"
:step="25"
:value="50"
show-ticks
show-tooltip
:tick-step="25"
/>
<!-- Filled and monochrome variants -->
<VueSlider label="Filled Variant" filled :value="60" />
<VueSlider label="Monochrome" monochrome :value="70" />
</section>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { VueSlider } from "agnosticui-core/slider/vue";
export default defineComponent({
components: { VueSlider },
});
</script>React
import { ReactSlider } from "agnosticui-core/slider/react";
export default function Example() {
const [value, setValue] = useState(50);
const [range, setRange] = useState<number | [number, number] | undefined>([
200, 800,
]);
return (
<section>
{/* Basic slider */}
<ReactSlider
label="Volume"
min={0}
max={100}
value={value}
onInput={(e) => setValue(e.detail.value as number)}
/>
{/* Dual range slider */}
<ReactSlider
label="Price Range"
dual
min={0}
max={1000}
value={range}
onInput={(e) => setRange(e.detail.value as [number, number])}
/>
{/* With ticks and tooltip */}
<ReactSlider
label="Brightness"
min={0}
max={100}
step={25}
value={50}
showTicks
showTooltip
tickStep={25}
/>
{/* Filled and monochrome variants */}
<ReactSlider label="Filled Variant" filled value={60} />
<ReactSlider label="Monochrome" monochrome value={70} />
</section>
);
}Lit (Web Components)
<script type="module">
import "agnosticui-core/slider";
</script>
<section>
<!-- Basic slider -->
<ag-slider label="Volume" min="0" max="100" value="75"></ag-slider>
<!-- Dual range slider -->
<ag-slider
label="Price Range"
dual
min="0"
max="1000"
.value="${[200,"
800]}
></ag-slider>
<!-- With ticks and tooltip -->
<ag-slider
label="Brightness"
min="0"
max="100"
step="25"
value="50"
show-ticks
show-tooltip
tick-step="25"
></ag-slider>
<!-- Filled and monochrome variants -->
<ag-slider label="Filled Variant" filled value="60"></ag-slider>
<ag-slider label="Monochrome" monochrome value="70"></ag-slider>
</section>Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | '' | Label text for the slider |
labelPosition | 'top' | 'start' | 'end' | 'bottom' | 'top' | Position of the label relative to the slider |
labelHidden | boolean | false | Visually hide the label (still accessible) |
noLabel | boolean | false | Remove label completely |
ariaLabel | string | '' | ARIA label when label is hidden |
min | number | 0 | Minimum value |
max | number | 100 | Maximum value |
step | number | 1 | Step increment for value changes |
value | number | [number, number] | 0 | Current value (single or dual range) |
dual | boolean | false | Enable dual range mode with two thumbs |
vertical | boolean | false | Vertical orientation |
filled | boolean | false | Filled thumb style (solid background) |
monochrome | boolean | false | Monochrome color scheme (adapts to dark mode) |
size | 'small' | 'default' | 'large' | 'default' | Size variant |
disabled | boolean | false | Disabled state prevents interaction |
readonly | boolean | false | Readonly state allows focus but prevents changes |
required | boolean | false | Required field indicator |
invalid | boolean | false | Invalid state for validation feedback |
errorMessage | string | '' | Error message text displayed when invalid |
helpText | string | '' | Helper text displayed below slider |
name | string | '' | Form field name for submission |
showTooltip | boolean | false | Show current value in tooltip while dragging |
showTicks | boolean | false | Display tick marks along track |
tickStep | number | 25 | Interval for tick marks |
Events
| Event | Framework | Detail | Description |
|---|---|---|---|
input | Vue: @inputReact: onInputLit: @input | { value: number | [number, number] } | Fired continuously while dragging the thumb. Use for real-time updates. |
change | Vue: @changeReact: onChangeLit: @change | { value: number | [number, number] } | Fired when the thumb is released after dragging. Use for final value updates. |
focus | Vue: @focusReact: onFocusLit: @focus | FocusEvent | Fired when slider receives focus. |
blur | Vue: @blurReact: onBlurLit: @blur | FocusEvent | Fired when slider loses focus. |
Note: The Slider component supports dual-dispatch event propagation: it dispatches both DOM CustomEvents (usable with addEventListener) and invokes callback props (.onInput, .onChange), giving you flexibility in how you handle events.
Event Usage Examples
Vue
<template>
<section>
<!-- Event handler with @input -->
<VueSlider
label="Volume"
:value="volume"
@input="handleInput"
@change="handleChange"
/>
<!-- v-model:value for two-way binding -->
<VueSlider label="Brightness" v-model:value="brightness" />
<!-- Dual range with events -->
<VueSlider
label="Price Range"
dual
:value="priceRange"
@input="handleRangeInput"
@change="handleRangeChange"
/>
<p>Current volume: {{ volume }}</p>
<p>Price range: ${{ priceRange[0] }} - ${{ priceRange[1] }}</p>
</section>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { VueSlider } from "agnosticui-core/slider/vue";
const volume = ref(50);
const brightness = ref(75);
const priceRange = ref([200, 800]);
const handleInput = (detail: { value: number | [number, number] }) => {
volume.value = detail.value as number;
console.log("Input (dragging):", detail.value);
};
const handleChange = (detail: { value: number | [number, number] }) => {
console.log("Change (released):", detail.value);
};
const handleRangeInput = (detail: { value: number | [number, number] }) => {
priceRange.value = detail.value as [number, number];
};
const handleRangeChange = (detail: { value: number | [number, number] }) => {
console.log("Range finalized:", detail.value);
};
</script>React
import { useState } from "react";
import { ReactSlider } from "agnosticui-core/slider/react";
export default function Example() {
const [volume, setVolume] = useState(50);
const [priceRange, setPriceRange] = useState([200, 800]);
return (
<section>
{/* Event handler with onInput */}
<ReactSlider
label="Volume"
value={volume}
onInput={(e) => {
setVolume(e.detail.value as number);
console.log("Input (dragging):", e.detail.value);
}}
onChange={(e) => {
console.log("Change (released):", e.detail.value);
}}
/>
{/* Dual range with events */}
<ReactSlider
label="Price Range"
dual
value={priceRange}
onInput={(e) => {
setPriceRange(e.detail.value as [number, number]);
}}
onChange={(e) => {
console.log("Range finalized:", e.detail.value);
}}
/>
{/* With focus/blur handlers */}
<ReactSlider
label="Brightness"
value={75}
onFocus={() => console.log("Slider focused")}
onBlur={() => console.log("Slider blurred")}
onInput={(e) => console.log("Value:", e.detail.value)}
/>
<p>Current volume: {volume}</p>
<p>
Price range: ${priceRange[0]} - ${priceRange[1]}
</p>
</section>
);
}Lit (Web Components)
<script type="module">
import "agnosticui-core/slider";
// Pattern 1: addEventListener (DOM events)
const slider1 = document.querySelector("#slider1");
slider1.addEventListener("input", (e) => {
console.log("Input event (dragging):", e.detail.value);
});
slider1.addEventListener("change", (e) => {
console.log("Change event (released):", e.detail.value);
});
// Pattern 2: Callback props
const slider2 = document.querySelector("#slider2");
slider2.onInput = (e) => {
console.log("Input callback:", e.detail.value);
};
slider2.onChange = (e) => {
console.log("Change callback:", e.detail.value);
};
// Pattern 3: Both patterns work (dual-dispatch)
const slider3 = document.querySelector("#slider3");
slider3.addEventListener("input", (e) => {
console.log("DOM event:", e.detail.value);
});
slider3.onInput = (e) => {
console.log("Callback also fired:", e.detail.value);
};
// Focus and blur events
const slider4 = document.querySelector("#slider4");
slider4.addEventListener("focus", (e) => {
console.log("Slider focused");
});
slider4.addEventListener("blur", (e) => {
console.log("Slider blurred");
});
</script>
<section>
<ag-slider
id="slider1"
label="addEventListener pattern"
value="50"
></ag-slider>
<ag-slider id="slider2" label="Callback prop pattern" value="50"></ag-slider>
<ag-slider
id="slider3"
label="Dual-dispatch (both patterns)"
value="50"
></ag-slider>
<ag-slider
id="slider4"
label="With focus/blur handlers"
value="50"
></ag-slider>
</section>Type:
export type SliderInputEvent = CustomEvent<{
value: number | [number, number];
}>;
export type SliderChangeEvent = CustomEvent<{
value: number | [number, number];
}>;CSS Shadow Parts
Shadow Parts allow you to style internal elements of the slider from outside the shadow DOM using the ::part() CSS selector.
| Part | Description |
|---|---|
ag-slider-container | The outer container wrapper |
ag-slider-label | The label element |
ag-slider-track | The slider track background |
ag-slider-progress | The progress/fill indicator |
ag-slider-thumb | The draggable thumb element |
ag-slider-ticks | Container for tick marks (when showTicks is true) |
ag-slider-tick | Individual tick mark |
ag-slider-tooltip | Tooltip showing current value (when showTooltip is true) |
ag-slider-help-text | Help text element |
ag-slider-error | Error message element |
Customization Examples
/* Customize track height and color */
ag-slider::part(ag-slider-track) {
height: 8px;
background: var(--ag-background-tertiary);
border-radius: var(--ag-radius-full);
}
/* Customize progress/fill color */
ag-slider::part(ag-slider-progress) {
background: linear-gradient(90deg, var(--ag-green-500), var(--ag-blue-500));
}
/* Customize thumb appearance */
ag-slider::part(ag-slider-thumb) {
width: 24px;
height: 24px;
border: 4px solid var(--ag-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
/* Style the label */
ag-slider::part(ag-slider-label) {
font-weight: 700;
color: var(--ag-primary);
font-size: 1.1rem;
}
/* Customize tooltip */
ag-slider::part(ag-slider-tooltip) {
background: var(--ag-primary);
color: var(--ag-white);
padding: 0.5rem 0.75rem;
border-radius: var(--ag-radius-md);
font-weight: 600;
}Accessibility
- Slider uses native
<input type="range">for proper keyboard navigation - Full keyboard support:
- Arrow keys: Increment/decrement by step
- Page Up/Down: Larger increments
- Home/End: Jump to min/max
- Tab: Navigate between thumbs in dual mode
- Proper
aria-valuenow,aria-valuemin,aria-valuemaxattributes - Live region announcements when value changes (for screen readers)
- Focus visible with customizable focus ring using design tokens
- Disabled state prevents interaction and is communicated to assistive technologies
Dark Mode Support
All variants automatically support dark mode through CSS design tokens:
- Default: Uses
--ag-primarywhich adapts to dark mode - Monochrome: Uses
--ag-text-primaryfor progress and borders- Light mode: Dark gray/black appearance
- Dark mode: Light gray/white appearance
- Monochrome + Filled: Thumb background adapts using
--ag-text-primary- Light mode: Dark filled thumb
- Dark mode: Light filled thumb for visibility
- Thumb always uses
--ag-whitebackground to ensure visibility in both modes
Notes
- Dual Range: When
dual={true}, value must be a tuple[min, max] - Form Integration: Sliders work with standard HTML forms via ElementInternals API
- Step Snapping: Values automatically snap to nearest step increment
- Vertical Mode: Set
vertical={true}for vertical orientation (useful for volume controls) - Ticks: Configure
showTicks={true}andtickStepfor visual markers - Tooltip: Set
showTooltip={true}to show current value while dragging - Lit: Properties can be set via attributes or property binding (e.g.,
.value=${50}) - All three implementations (Lit, React, Vue) share the same underlying styles and behavior