Slider
Control for selecting a numeric value within a range.
TailwindCSS
Vanilla JS
package showcase
import "github.com/axzilla/templui/components"
templ SliderDefault() {
<div class="w-full max-w-sm">
@components.Slider() {
@components.SliderInput()
}
</div>
}
package components
import (
"fmt"
"github.com/axzilla/templui/utils"
)
type SliderProps struct {
ID string
Class string
Attributes templ.Attributes
Name string
Min int
Max int
Step int
Value int
Disabled bool
}
type SliderValueProps struct {
ID string
Class string
Attributes templ.Attributes
For string
}
templ Slider(props ...SliderProps) {
{{ var p SliderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<div
if p.ID != "" {
id={ p.ID + "-container" }
}
class={ utils.TwMerge("w-full", p.Class) }
data-slider
data-slider-id={ p.ID }
{ p.Attributes... }
>
{ children... }
</div>
}
templ SliderInput(props ...SliderProps) {
{{ var p SliderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<input
type="range"
id={ p.ID }
if p.Name != "" {
name={ p.Name }
}
if p.Value != 0 {
value={ fmt.Sprintf("%d", p.Value) }
}
if p.Min != 0 {
min={ fmt.Sprintf("%d", p.Min) }
}
if p.Max != 0 {
max={ fmt.Sprintf("%d", p.Max) }
}
if p.Step != 0 {
step={ fmt.Sprintf("%d", p.Step) }
}
class={
utils.TwMerge(
"w-full h-2 rounded-full bg-secondary appearance-none cursor-pointer",
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4",
"[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-primary",
"[&::-webkit-slider-thumb]:hover:bg-primary/90",
"[&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:border-0",
"[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-primary",
"[&::-moz-range-thumb]:hover:bg-primary/90",
"disabled:opacity-50 disabled:cursor-not-allowed",
p.Class,
),
}
disabled?={ p.Disabled }
{ p.Attributes... }
/>
}
templ SliderValue(props ...SliderValueProps) {
{{ var p SliderValueProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<span
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("text-sm text-muted-foreground", p.Class) }
data-slider-value-for={ p.For }
{ p.Attributes... }
></span>
}
templ SliderScript() {
{{ handler := templ.NewOnceHandle() }}
@handler.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', () => {
const sliders = document.querySelectorAll('[data-slider]');
sliders.forEach(slider => {
const sliderId = slider.dataset.sliderId;
const input = slider.querySelector('input[type="range"]');
const valueElements = document.querySelectorAll(`[data-slider-value-for="${sliderId}"]`);
if (input) {
// Initial Setup
valueElements.forEach(el => {
el.textContent = input.value;
});
// Event Listener
input.addEventListener('input', () => {
valueElements.forEach(el => {
el.textContent = input.value;
});
});
}
});
});
</script>
}
}
Usage
1. Add the script to your page/layout:
// Option A: All components (recommended)
@utils.ComponentScripts()
// Option B: Just Slider
@components.SliderScript()
2. Use the component:
@components.Slider(components.SliderProps{...})
Examples
Value
package showcase
import "github.com/axzilla/templui/components"
templ SliderValue() {
<div class="w-full max-w-sm">
@components.Slider(components.SliderProps{
ID: "slider-value",
Name: "slider-value",
Value: 75,
Min: 0,
Max: 100,
Step: 1,
}) {
<div class="flex justify-end mb-1">
@components.SliderValue(components.SliderValueProps{
For: "slider-value",
})
</div>
@components.SliderInput(components.SliderProps{
ID: "slider-value",
Name: "slider-value",
Value: 75,
Min: 0,
Max: 100,
Step: 1,
})
}
</div>
}
package components
import (
"fmt"
"github.com/axzilla/templui/utils"
)
type SliderProps struct {
ID string
Class string
Attributes templ.Attributes
Name string
Min int
Max int
Step int
Value int
Disabled bool
}
type SliderValueProps struct {
ID string
Class string
Attributes templ.Attributes
For string
}
templ Slider(props ...SliderProps) {
{{ var p SliderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<div
if p.ID != "" {
id={ p.ID + "-container" }
}
class={ utils.TwMerge("w-full", p.Class) }
data-slider
data-slider-id={ p.ID }
{ p.Attributes... }
>
{ children... }
</div>
}
templ SliderInput(props ...SliderProps) {
{{ var p SliderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<input
type="range"
id={ p.ID }
if p.Name != "" {
name={ p.Name }
}
if p.Value != 0 {
value={ fmt.Sprintf("%d", p.Value) }
}
if p.Min != 0 {
min={ fmt.Sprintf("%d", p.Min) }
}
if p.Max != 0 {
max={ fmt.Sprintf("%d", p.Max) }
}
if p.Step != 0 {
step={ fmt.Sprintf("%d", p.Step) }
}
class={
utils.TwMerge(
"w-full h-2 rounded-full bg-secondary appearance-none cursor-pointer",
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4",
"[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-primary",
"[&::-webkit-slider-thumb]:hover:bg-primary/90",
"[&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:border-0",
"[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-primary",
"[&::-moz-range-thumb]:hover:bg-primary/90",
"disabled:opacity-50 disabled:cursor-not-allowed",
p.Class,
),
}
disabled?={ p.Disabled }
{ p.Attributes... }
/>
}
templ SliderValue(props ...SliderValueProps) {
{{ var p SliderValueProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<span
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("text-sm text-muted-foreground", p.Class) }
data-slider-value-for={ p.For }
{ p.Attributes... }
></span>
}
templ SliderScript() {
{{ handler := templ.NewOnceHandle() }}
@handler.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', () => {
const sliders = document.querySelectorAll('[data-slider]');
sliders.forEach(slider => {
const sliderId = slider.dataset.sliderId;
const input = slider.querySelector('input[type="range"]');
const valueElements = document.querySelectorAll(`[data-slider-value-for="${sliderId}"]`);
if (input) {
// Initial Setup
valueElements.forEach(el => {
el.textContent = input.value;
});
// Event Listener
input.addEventListener('input', () => {
valueElements.forEach(el => {
el.textContent = input.value;
});
});
}
});
});
</script>
}
}
Steps
package showcase
import "github.com/axzilla/templui/components"
templ SliderSteps() {
<div class="w-full max-w-sm">
@components.Slider(components.SliderProps{
ID: "slider-steps",
Name: "slider-steps",
Value: 100,
Min: 0,
Max: 200,
Step: 25,
}) {
<div class="flex justify-between items-center mb-1">
@components.Label() {
Zoom Level
}
<div class="flex items-center">
@components.SliderValue(components.SliderValueProps{
For: "slider-steps",
})
</div>
</div>
@components.SliderInput(components.SliderProps{
ID: "slider-steps",
Name: "slider-steps",
Value: 100,
Min: 0,
Max: 200,
Step: 25,
})
}
</div>
}
package components
import (
"fmt"
"github.com/axzilla/templui/utils"
)
type SliderProps struct {
ID string
Class string
Attributes templ.Attributes
Name string
Min int
Max int
Step int
Value int
Disabled bool
}
type SliderValueProps struct {
ID string
Class string
Attributes templ.Attributes
For string
}
templ Slider(props ...SliderProps) {
{{ var p SliderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<div
if p.ID != "" {
id={ p.ID + "-container" }
}
class={ utils.TwMerge("w-full", p.Class) }
data-slider
data-slider-id={ p.ID }
{ p.Attributes... }
>
{ children... }
</div>
}
templ SliderInput(props ...SliderProps) {
{{ var p SliderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<input
type="range"
id={ p.ID }
if p.Name != "" {
name={ p.Name }
}
if p.Value != 0 {
value={ fmt.Sprintf("%d", p.Value) }
}
if p.Min != 0 {
min={ fmt.Sprintf("%d", p.Min) }
}
if p.Max != 0 {
max={ fmt.Sprintf("%d", p.Max) }
}
if p.Step != 0 {
step={ fmt.Sprintf("%d", p.Step) }
}
class={
utils.TwMerge(
"w-full h-2 rounded-full bg-secondary appearance-none cursor-pointer",
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4",
"[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-primary",
"[&::-webkit-slider-thumb]:hover:bg-primary/90",
"[&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:border-0",
"[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-primary",
"[&::-moz-range-thumb]:hover:bg-primary/90",
"disabled:opacity-50 disabled:cursor-not-allowed",
p.Class,
),
}
disabled?={ p.Disabled }
{ p.Attributes... }
/>
}
templ SliderValue(props ...SliderValueProps) {
{{ var p SliderValueProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<span
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("text-sm text-muted-foreground", p.Class) }
data-slider-value-for={ p.For }
{ p.Attributes... }
></span>
}
templ SliderScript() {
{{ handler := templ.NewOnceHandle() }}
@handler.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', () => {
const sliders = document.querySelectorAll('[data-slider]');
sliders.forEach(slider => {
const sliderId = slider.dataset.sliderId;
const input = slider.querySelector('input[type="range"]');
const valueElements = document.querySelectorAll(`[data-slider-value-for="${sliderId}"]`);
if (input) {
// Initial Setup
valueElements.forEach(el => {
el.textContent = input.value;
});
// Event Listener
input.addEventListener('input', () => {
valueElements.forEach(el => {
el.textContent = input.value;
});
});
}
});
});
</script>
}
}
Disabled
package showcase
import "github.com/axzilla/templui/components"
templ SliderDisabled() {
<div class="w-full max-w-sm">
@components.Slider(components.SliderProps{
ID: "slider-disabled",
Name: "slider-disabled",
Value: 20,
Min: -20,
Max: 200,
Step: 20,
Disabled: true,
}) {
<div class="flex justify-between items-center mb-1">
@components.Label() {
Volume
}
@components.SliderValue(components.SliderValueProps{
For: "slider-disabled",
})
</div>
@components.SliderInput(components.SliderProps{
ID: "slider-disabled",
Name: "slider-disabled",
Value: 20,
Min: -20,
Max: 200,
Step: 20,
Disabled: true,
})
}
</div>
}
package components
import (
"fmt"
"github.com/axzilla/templui/utils"
)
type SliderProps struct {
ID string
Class string
Attributes templ.Attributes
Name string
Min int
Max int
Step int
Value int
Disabled bool
}
type SliderValueProps struct {
ID string
Class string
Attributes templ.Attributes
For string
}
templ Slider(props ...SliderProps) {
{{ var p SliderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<div
if p.ID != "" {
id={ p.ID + "-container" }
}
class={ utils.TwMerge("w-full", p.Class) }
data-slider
data-slider-id={ p.ID }
{ p.Attributes... }
>
{ children... }
</div>
}
templ SliderInput(props ...SliderProps) {
{{ var p SliderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<input
type="range"
id={ p.ID }
if p.Name != "" {
name={ p.Name }
}
if p.Value != 0 {
value={ fmt.Sprintf("%d", p.Value) }
}
if p.Min != 0 {
min={ fmt.Sprintf("%d", p.Min) }
}
if p.Max != 0 {
max={ fmt.Sprintf("%d", p.Max) }
}
if p.Step != 0 {
step={ fmt.Sprintf("%d", p.Step) }
}
class={
utils.TwMerge(
"w-full h-2 rounded-full bg-secondary appearance-none cursor-pointer",
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4",
"[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-primary",
"[&::-webkit-slider-thumb]:hover:bg-primary/90",
"[&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:border-0",
"[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-primary",
"[&::-moz-range-thumb]:hover:bg-primary/90",
"disabled:opacity-50 disabled:cursor-not-allowed",
p.Class,
),
}
disabled?={ p.Disabled }
{ p.Attributes... }
/>
}
templ SliderValue(props ...SliderValueProps) {
{{ var p SliderValueProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<span
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("text-sm text-muted-foreground", p.Class) }
data-slider-value-for={ p.For }
{ p.Attributes... }
></span>
}
templ SliderScript() {
{{ handler := templ.NewOnceHandle() }}
@handler.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', () => {
const sliders = document.querySelectorAll('[data-slider]');
sliders.forEach(slider => {
const sliderId = slider.dataset.sliderId;
const input = slider.querySelector('input[type="range"]');
const valueElements = document.querySelectorAll(`[data-slider-value-for="${sliderId}"]`);
if (input) {
// Initial Setup
valueElements.forEach(el => {
el.textContent = input.value;
});
// Event Listener
input.addEventListener('input', () => {
valueElements.forEach(el => {
el.textContent = input.value;
});
});
}
});
});
</script>
}
}
External Value
External value (linked to the slider):
%
package showcase
import "github.com/axzilla/templui/components"
templ SliderExternalValue() {
<div class="space-y-6 w-full max-w-sm">
<div>
@components.Slider(components.SliderProps{
ID: "slider-external-value",
Name: "slider-external-value",
Value: 50,
Min: 0,
Max: 100,
Step: 1,
}) {
@components.SliderInput(components.SliderProps{
ID: "slider-external-value",
Name: "slider-external-value",
Value: 50,
Min: 0,
Max: 100,
Step: 1,
})
}
</div>
<div class="bg-muted p-4 rounded-md">
<p class="text-sm text-muted-foreground mb-2">External value (linked to the slider):</p>
<div class="text-3xl font-bold flex gap-1">
@components.SliderValue(components.SliderValueProps{
For: "slider-external-value",
Class: "text-3xl font-bold text-primary",
})
%
</div>
</div>
</div>
}
package components
import (
"fmt"
"github.com/axzilla/templui/utils"
)
type SliderProps struct {
ID string
Class string
Attributes templ.Attributes
Name string
Min int
Max int
Step int
Value int
Disabled bool
}
type SliderValueProps struct {
ID string
Class string
Attributes templ.Attributes
For string
}
templ Slider(props ...SliderProps) {
{{ var p SliderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<div
if p.ID != "" {
id={ p.ID + "-container" }
}
class={ utils.TwMerge("w-full", p.Class) }
data-slider
data-slider-id={ p.ID }
{ p.Attributes... }
>
{ children... }
</div>
}
templ SliderInput(props ...SliderProps) {
{{ var p SliderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<input
type="range"
id={ p.ID }
if p.Name != "" {
name={ p.Name }
}
if p.Value != 0 {
value={ fmt.Sprintf("%d", p.Value) }
}
if p.Min != 0 {
min={ fmt.Sprintf("%d", p.Min) }
}
if p.Max != 0 {
max={ fmt.Sprintf("%d", p.Max) }
}
if p.Step != 0 {
step={ fmt.Sprintf("%d", p.Step) }
}
class={
utils.TwMerge(
"w-full h-2 rounded-full bg-secondary appearance-none cursor-pointer",
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4",
"[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-primary",
"[&::-webkit-slider-thumb]:hover:bg-primary/90",
"[&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:border-0",
"[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-primary",
"[&::-moz-range-thumb]:hover:bg-primary/90",
"disabled:opacity-50 disabled:cursor-not-allowed",
p.Class,
),
}
disabled?={ p.Disabled }
{ p.Attributes... }
/>
}
templ SliderValue(props ...SliderValueProps) {
{{ var p SliderValueProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<span
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("text-sm text-muted-foreground", p.Class) }
data-slider-value-for={ p.For }
{ p.Attributes... }
></span>
}
templ SliderScript() {
{{ handler := templ.NewOnceHandle() }}
@handler.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', () => {
const sliders = document.querySelectorAll('[data-slider]');
sliders.forEach(slider => {
const sliderId = slider.dataset.sliderId;
const input = slider.querySelector('input[type="range"]');
const valueElements = document.querySelectorAll(`[data-slider-value-for="${sliderId}"]`);
if (input) {
// Initial Setup
valueElements.forEach(el => {
el.textContent = input.value;
});
// Event Listener
input.addEventListener('input', () => {
valueElements.forEach(el => {
el.textContent = input.value;
});
});
}
});
});
</script>
}
}