ee8797c142
Co-authored-by: Copilot <copilot@github.com>
6.2 KiB
6.2 KiB
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 0–100 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: "10–100")
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 JSinputevent listener:slider.addEventListener('input', e => output.value = e.target.value). This can be added viahxAttrs: "oninput=\"document.getElementById('vol-val').textContent=this.value\"". valueis the initial server-rendered position. After the user moves the slider, only the form submission captures the new value.- Use
stepto snap to meaningful increments (e.g.step: 5for a 0–100 percentage slider). accent-primaryis supported in all modern browsers and requires no custom CSS.accent-primaryis 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")]