Files
Htmx/docs/Components/Slider.md
T
2026-05-04 19:57:48 +05:00

6.2 KiB
Raw Blame History

Slider

A styled <input type="range"> with optional label and description. Supports min/max/step/value and HTMX attributes.


HTML structure

div.flex.flex-col.gap-1.5
  label[for={id}].text-sm.font-medium        ← omitted when label is empty
    {label}
  input[type=range, id, name, min, max, step, value, class, $$HxAttrs$$]
  p.text-sm.text-muted-foreground            ← omitted when description is empty
    {description}

CSS mechanics

Class Effect
w-full h-2 rounded-lg appearance-none cursor-pointer accent-primary Full-width, pill-shaped track; thumb follows primary color
bg-secondary Track fill color
accent-primary Thumb and active track color follows --color-primary

Constructor signature

public Slider(
    string id,
    string name         = "",
    int    min          = 0,
    int    max          = 100,
    int    step         = 1,
    int    value        = 50,
    string label        = "",
    string description  = "",
    string extraClasses = "",
    string hxAttrs      = "")
Parameter Description
id Element id and label for target
name Form field name
min Minimum value (default: 0)
max Maximum value (default: 100)
step Increment step (default: 1)
value Initial value (default: 50)
label Optional visible label
description Optional helper text
extraClasses Additional Tailwind classes on the input
hxAttrs Verbatim HTMX / data attributes

Usage examples

Basic 0100 slider

new Slider(
    id:    "volume",
    name:  "volume",
    label: "Volume")

Fixed range with step

new Slider(
    id:    "brightness",
    name:  "brightness",
    min:   10,
    max:   100,
    step:  10,
    value: 70,
    label: "Brightness",
    description: "10100")

Live HTMX update

new Slider(
    id:      "fontSize",
    name:    "fontSize",
    min:     12,
    max:     24,
    value:   16,
    label:   "Font size",
    hxAttrs: """hx-post="/settings/font-size" hx-trigger="change" hx-include="[name='fontSize']"""")

Reading in a form handler

public record Command([property: FromForm] int Volume);

// command.Volume is the slider value at submit time

Tips and tricks

  • Display the current numeric value next to the slider by adding a small <output> element and a JS input event listener: slider.addEventListener('input', e => output.value = e.target.value). This can be added via hxAttrs: "oninput=\"document.getElementById('vol-val').textContent=this.value\"".
  • value is the initial server-rendered position. After the user moves the slider, only the form submission captures the new value.
  • Use step to snap to meaningful increments (e.g. step: 5 for a 0100 percentage slider).
  • accent-primary is supported in all modern browsers and requires no custom CSS.
  • accent-primary is supported in all modern browsers and requires no custom CSS.

Complete page example

Templates/AudioSettingsPage.htmx

<div class="max-w-md mx-auto py-10">
  <h1 class="text-2xl font-bold mb-6">Audio settings</h1>
  <form method="post" action="/settings/audio">
    $$AntiforgeryToken$$
    <div class="space-y-6 mb-8">
      $$VolumeSlider$$
      $$BassSlider$$
      $$TrebleSlider$$
    </div>
    $$SaveBtn$$
  </form>
  $$SuccessAlert$$
</div>

Templates/AudioSettingsPage.htmx.cs

namespace Htmx.ApiDemo.Templates;

public sealed class AudioSettingsPage : AudioSettingsPageBase
{
    private readonly IHtmxComponent _volume;
    private readonly IHtmxComponent _bass;
    private readonly IHtmxComponent _treble;
    private readonly IHtmxComponent _save;
    private readonly IHtmxComponent _success;
    private readonly byte[] _afToken;

    public AudioSettingsPage(
        IAntiforgery af,
        HttpContext ctx,
        AudioPrefs? prefs = null,
        bool saved = false)
    {
        var tokens = af.GetAndStoreTokens(ctx);
        _afToken = $"""<input type="hidden" name="{tokens.FormFieldName}" value="{tokens.RequestToken}">""".ToUtf8Bytes();

        _volume  = new Components.Slider(id: "volume",  name: "volume",  label: "Volume",  value: prefs?.Volume  ?? 70, description: "0  100");
        _bass    = new Components.Slider(id: "bass",    name: "bass",    label: "Bass",    value: prefs?.Bass    ?? 50, min: -10, max: 10, step: 1);
        _treble  = new Components.Slider(id: "treble",  name: "treble",  label: "Treble",  value: prefs?.Treble  ?? 50, min: -10, max: 10, step: 1);
        _save    = new Components.Button("Save", type: "submit");
        _success = saved ? new Components.Alert(title: "Audio settings saved.") : HtmxEmpty.Instance;
    }

    protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afToken);
    protected override void RenderVolumeSlider(HtmxRenderContext ctx)     => _volume.Render(ctx.Next());
    protected override void RenderBassSlider(HtmxRenderContext ctx)       => _bass.Render(ctx.Next());
    protected override void RenderTrebleSlider(HtmxRenderContext ctx)     => _treble.Render(ctx.Next());
    protected override void RenderSaveBtn(HtmxRenderContext ctx)          => _save.Render(ctx.Next());
    protected override void RenderSuccessAlert(HtmxRenderContext ctx)     => _success.Render(ctx.Next());
}

POST handler

[Handler]
[MapPost("/settings/audio")]
public static partial class PostAudioSettingsHandler
{
    public record Command(
        [property: FromForm] int Volume,
        [property: FromForm] int Bass,
        [property: FromForm] int Treble);

    private static Task<IResult> HandleAsync(
        [AsParameters] Command cmd, HttpContext ctx, IAntiforgery af, CancellationToken ct)
    {
        var prefs = new AudioPrefs(cmd.Volume, cmd.Bass, cmd.Treble);
        // Persist prefs…
        return ctx.WriteHtmxPage(
            new AudioSettingsPage(af, ctx, prefs, saved: true), title: "Audio settings");
    }
}

public record AudioPrefs(int Volume, int Bass, int Treble);

AppJsonSerializerContext.cs

[JsonSerializable(typeof(PostAudioSettingsHandler.Command), TypeInfoPropertyName = "AudioSettingsCommand")]