Range

Slider input

Intent

Range is a slider for selecting a numeric value within a defined min / max -- volume, brightness, opacity, weight, any continuous numeric setting where direct manipulation beats a typed number. It styles a native <input type="range"> with appearance: none and rebuilds the track and thumb via vendor pseudo-elements, normalizing the visual across Chrome, Safari, Edge, and Firefox.

Reach for a numeric input when the user needs to type a precise value, when the valid range is unbounded or very large, or when the readout matters more than the manipulation. Range is for feel, not precision.

Basic usage

Add class="input" to a native <input type="range">. Provide min, max, and an initial value; step is optional and defaults to 1.

<div class="stack">
    <label for="demo-vol">Volume</label>
    <input id="demo-vol" type="range" class="input" min="0" max="100" value="50"/>
</div>

Variants

Size

Track height and thumb size scale together so the thumb stays vertically centered at every size.

<div class="stack">
    <label for="demo-sm">Small</label>
    <input id="demo-sm" type="range" class="input is-sm" min="0" max="100" value="25"/>

    <label for="demo-default">Default</label>
    <input id="demo-default" type="range" class="input" min="0" max="100" value="50"/>

    <label for="demo-lg">Large</label>
    <input id="demo-lg" type="range" class="input is-lg" min="0" max="100" value="75"/>
</div>

Tick marks via <datalist>

A <datalist> linked to the range via the list attribute remains useful even though brio doesn't paint its tick marks visually: browsers that support snap-to-step still snap the thumb to the datalist's option values, and assistive tech can read the <option>'s label attribute to announce stops as the user navigates. Markup pattern:

<input id="vol" type="range" class="input"
       min="0" max="100" value="50" step="5"
       list="vol-stops">
<datalist id="vol-stops">
    <option value="0" label="Mute"></option>
    <option value="50" label="Half"></option>
    <option value="100" label="Full"></option>
</datalist>

Why ticks aren't visually rendered. brio's custom chrome stripes the slider's native rendering with appearance: none so the track and thumb can be styled to match the rest of the input family. The browser's tick-mark drawing is part of that native rendering -- when we strip it, the ticks go too. The trade-off is the same one Bootstrap, Tailwind, and most CSS systems make: visual chrome consistency in exchange for the native ticks.

If you need visible ticks, render them in adjacent markup below the slider -- a .cluster.is-between of small text labels spaced at the option positions reads more clearly than thin tick lines anyway, and you get to position the labels precisely. The <datalist> can stay (still useful for snap-step and ARIA) while the visual labels live externally.

<div class="stack is-tight">
    <label for="demo-ticks">Volume</label>
    <input id="demo-ticks" type="range" class="input" min="0" max="100" value="50" step="5" list="demo-tick-marks"/>
    <datalist id="demo-tick-marks">
        <option value="0" label="Mute"></option>
        <option value="25"></option>
        <option value="50" label="Half"></option>
        <option value="75"></option>
        <option value="100" label="Full"></option>
    </datalist>
    <div class="cluster is-between text-muted text--1" aria-hidden="true">
        <span>Mute</span>
        <span>Half</span>
        <span>Full</span>
    </div>
</div>

State and interaction

  • Hover -- thumb border darkens, signalling the thumb as the interactive target before any click.
  • Focus (keyboard): thumb border shifts to --color-primary and a focus ring appears around the thumb. Mouse focus (clicking the thumb) is intentionally unstyled -- the thumb's position is the feedback. Arrow keys increment / decrement the value by step; Page Up / Page Down jump by a larger amount; Home / End go to min / max.
  • Disabled -- the input fades to 50% opacity and pointer is disallowed.
<div class="stack">
    <label for="demo-disabled">Disabled</label>
    <input id="demo-disabled" type="range" class="input" min="0" max="100" value="40" disabled=""/>
</div>

Visual language

Range matches the rest of the input family in tone -- same surface as text inputs, checkboxes, and radios; primary reserved for focus. The thumb border is thicker than the family default because the thumb is a floating element on a track, not a contained box -- a single-width neutral outline disappears against a light page. There is no filled-portion progress fill: the thumb's position is the indicator. There is no box-shadow on the thumb; brio is flat throughout.

Class reference

  • .input -- the universal form-input class. On a <input type="range"> brio applies the slider chrome described above. Without the class, the browser default renders.
  • .input.is-sm, .input.is-lg on the range input -- size variants. Track height and thumb size scale together.

Customization

Hover and focus states drive --input-range-thumb-border-color through cascade, so overriding it once recolors every state automatically. Range styling lives in src/css/input.css alongside the rest of the form-input family; the range section sits after the toggle switch.

Accessibility

  • Always pair with a label. An unlabeled slider has no accessible name -- screen readers announce only the value. Use <label for="..."> with a matching id, or wrap the input in a label, or set aria-label on the input if no visible label fits the design.
  • Announce the current value when relevant. For sliders whose value matters to surrounding content (a brightness slider that previews live, a price filter that updates a listing), pair the range with a value display (<output> bound via for) or aria-valuetext when the raw number isn't meaningful (e.g., a quality slider with named stops).
  • Don't use range for binary or low-cardinality choices. A slider with only two or three steps is harder to use accurately than a checkbox / radio group / select with the same options. Range is for ranges.
  • Forced-colors mode. Under @media (forced-colors: active) brio reverts the slider to native rendering (appearance: auto) so high-contrast users get the OS's accessible chrome rather than our custom paint -- which can lose contrast in some palettes. Same fallback as checkbox and radio.

CSS Reference: Input Range

For input range reference see the input page