Canvas Design System
Main Site Tokens

Range

A slider for a value picked by feel along a continuous scale, built on the native range input.

When to Use

Use when: The reader is choosing roughly how much, and seeing the effect matters more than the number: a break length, a mix between two things, how much of a figure each side takes. The bare variant is for a handle the page draws itself, such as a divider between two panes.
Don't use when: The exact value matters or will be typed: use Input with type="number", or Stepper for fixed jumps the reader counts. For a handful of named choices use Select or Radio Card Group.

Variants

Default

The browser's own slider, tinted with --brand through accent-color. No script is needed for it to work. Pass valueText so a screen reader hears what the value means, and update aria-valuetext as it moves.

15 minutes

<?= ws_range('break_length', [
    'label'     => 'Break length',
    'min'       => 5,
    'max'       => 30,
    'step'      => 5,
    'value'     => 15,
    'valueText' => '15 minutes',
]) ?>

Bare

The track and thumb go transparent and the page draws the handle underneath. The input stays real: focusable, labelled (here with hideLabel), and moved with the arrow keys. The focus ring goes round the thumb, where the drawn handle is. The input is one thumb tall (--ws-range-thumb, 44px by default), so position its wrapper centred on the handle and spanning the travel plus half a thumb each side.

Before
After
<?= ws_range('split', [
    'label'     => 'Move the divider between before and after',
    'hideLabel' => true,
    'min'       => 20,
    'max'       => 80,
    'value'     => 50,
    'valueText' => 'Before 50%, After 50%',
    'variant'   => 'bare',
]) ?>

States

Set by the session you joined

StateBehavior
DefaultThe browser paints the track and thumb; accent-color fills the travelled part with the brand colour.
HoverPointer cursor on the default variant, ew-resize on the bare one. The bare variant's drawn handle can react with :has(.ws-range__input:hover).
FocusThe platform focus ring (--focus-ring) round the input on the default variant, and round the thumb on the bare one.
DisabledNative disabled plus .ws-range--disabled, which dims the whole field. Skipped by Tab.
<?= ws_range('room_size', ['label' => 'Room size', 'value' => 12, 'disabled' => true]) ?>

Real-World Usage

The homepage candidate at /index4.php uses the bare variant for its hinge: the page draws a divider and a grip between how a workshop lives now and how it lives in Workshopr, and the range sits over the grip so a reader can drag it or move it with the arrow keys. The page's script listens for input, moves the split and rewrites aria-valuetext:

const range = document.querySelector('#i4-split');
range.addEventListener('input', () => {
    const now = Number(range.value);
    figure.style.setProperty('--i4-split', now);
    range.setAttribute('aria-valuetext', `How it lives now ${now}%, In Workshopr ${100 - now}%`);
});

Options

OptionTypeDefaultPurpose
$namestring''Positional. The input's name.
labelstringnullA real <label for>. Always pass one: a range with no name is announced only as "slider".
hideLabelboolfalseHides the label visually (.ws-visually-hidden) while it still names the input. For when the page shows the meaning another way.
descriptionstringnullSub-text under the slider, wired as aria-describedby.
min / maxint|float0 / 100The ends of the scale.
stepint|float1Distance between values, and how far one arrow key moves it.
valueint|floathalfwayThe starting value.
valueTextstringnullWhat the starting value means, as aria-valuetext ("15 minutes"). Update it from script as the value moves.
variantstring'default'default or bare.
disabledboolfalseNative disabled state plus the dimmed wrapper class.
idstring'ws-range-' . uniqid()Input ID. Set it when script needs a stable handle.
classstring''Additional classes on the wrapper, which is what a page positions.
attrsarray[]Extra attributes on the input, for data-* hooks.

Accessibility

ConcernBehavior
SemanticsA native <input type="range">, so the slider role, value, min and max come from the browser. Both variants keep it; the bare one only makes its paint transparent.
KeyboardNative: Tab to reach, arrow keys move one step, Page Up/Page Down move further, Home/End jump to the ends.
LabellingA <label for>, visually hidden with hideLabel and never removed. The description is announced after it through aria-describedby.
ValueWithout aria-valuetext a screen reader reads the bare number. Pass valueText and keep it current from script so it hears "15 minutes" or "Before 40%, After 60%".
Target sizeThe default variant's box is 44px tall. The bare variant's thumb is 44px square unless the page sets --ws-range-thumb; keep it at 44px or more.
Forced coloursThe bare variant's input keeps a transparent outline rather than none, so Windows High Contrast repaints a visible ring.

Tokens

TokenUsed for
--brandaccent-color on the default variant
--focus-ring / --focus-ring-offsetFocus ring round the default variant
--focus-ring-width / --focus-ring-color / --focus-ring-haloFocus ring and halo round the bare variant's thumb
--text-small / --text-mutedDescription
--space-2Gap between label, slider and description
--radius-fullBare thumb shape

CSS Classes

ClassPurpose
.ws-rangeField wrapper: label, slider, description
.ws-range--bareTransparent track and thumb for a page-drawn handle; sets --ws-range-thumb
.ws-range--disabledDimmed disabled field
.ws-range__labelThe label (also carries .ws-field__label)
.ws-range__inputThe range input
.ws-range__descriptionSub-text

Files

FilePurpose
includes/components/helpers.phpws_range() helper function
includes/components/range.phpTemplate: label, input and description wiring
includes/components/components.cssStyles (.ws-range rules)