SliderBar

Updated: Sep 24, 2026
SliderBar draws a value between minimumValue and maximumValue as a filled portion of a pill-shaped track. By default it is display-only: it reports role="progressbar", takes no focus, and handles no keys. Passing onChange opts it into an interactive mode where it becomes a real role="slider" with a tab stop and arrow-key adjustment.
It is the low-level bar used by higher-level controls. For a labelled setting row prefer ListItem with showSlider; for a compact adjustable control prefer IsolatedControl; for read-only progress prefer ProgressIndicator. Reach for SliderBar directly when you need a bare bar and already own the surrounding label and layout.

Usage

Display-only, as part of a larger composition:
import { SliderBar, SliderBarSize } from '@wearables-ui-toolkit/mrbd';

<SliderBar
  value={bytesDownloaded}
  maximumValue={bytesTotal}
  size={SliderBarSize.THIN}
  animated
  aria-label="Download progress"
/>;
Interactive, driven entirely by the keyboard:
import { useState } from 'react';
import { SliderBar, SliderBarState } from '@wearables-ui-toolkit/mrbd';

export function BrightnessBar() {
  const [level, setLevel] = useState(0.5);
  const [isFocused, setIsFocused] = useState(false);

  return (
    <SliderBar
      value={level}
      onChange={setLevel}
      incrementPercentage={0.05}
      state={isFocused ? SliderBarState.FOCUSED : SliderBarState.IDLE}
      onFocus={() => setIsFocused(true)}
      onBlur={() => setIsFocused(false)}
      animated
      aria-label="Brightness"
    />
  );
}
A horizontal bar takes its width from the layout, so give it a sized parent:
.brightness-row {
  display: flex;
  align-items: center;
  gap: var(--uit-spacing-medium);
  padding-inline: var(--uit-spacing-large);
}

.brightness-row .bar {
  flex: 1 1 auto;
}
A vertical bar stretches to the height of its parent, so the parent — not the bar — must have a height:
import { SliderBar, SliderBarOrientation } from '@wearables-ui-toolkit/mrbd';

<div className="level-column">
  <SliderBar
    value={level}
    orientation={SliderBarOrientation.VERTICAL}
    aria-label="Level"
  />
</div>;

Props

The full prop type is SliderBarProps.
PropTypeDefaultDescription
minimumValue
number
0
Lower bound of the range.
maximumValue
number
1
Upper bound of the range.
value
number
0
Current value, clamped into the range before rendering and before it is announced.
state
SliderBarState
SliderBarState.IDLE
Visual state. Drives the fill colour and, with the default size, the expanded thickness. The component does not derive this from focus; you set it.
size
SliderBarSize
SliderBarSize.DEFAULT
Thickness variant. DEFAULT expands when focused, THIN stays constant.
orientation
SliderBarOrientation
SliderBarOrientation.HORIZONTAL
Fill direction: left to right, or bottom to top when vertical.
shouldExpandOnFocus
boolean
true
Whether the FOCUSED state thickens the track. Ignored by THIN.
animated
boolean
false
Whether the fill, the focus thickening, and the colour change transition smoothly instead of snapping.
onChange
(newValue: number) => void
—
Enables interactive mode. Called with the next value on each arrow key press; the parent must apply it to value.
incrementPercentage
number
0.1
Step per key press, as a fraction of maximumValue minus minimumValue.
disabled
boolean
false
Turns interactive mode back off and marks the element as disabled.
SliderBarProps also accepts the standard div attribute surface — className, style, aria-label, data-* (including data-uit-*), and DOM event handlers such as onFocus and onBlur — which is spread onto the root element. There is no onChange DOM handler slot: the onChange above replaces it.
role, tabIndex, aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext, aria-disabled, and aria-hidden are applied after the rest props and cannot be overridden. aria-label is honoured when you supply one. onKeyDown behaves differently again: in interactive mode a handler you pass takes the place of the built-in arrow stepping, so the bar stops responding to Left and Right; in display-only mode it is dropped and never attached.

Types and enums

SliderBarState
  • IDLE — resting thickness and the idle fill colour (--uit-color-control-idle).
  • FOCUSED — the active fill colour (--uit-color-control-active), and expanded thickness with the default size.
SliderBarSize
  • DEFAULT — expands on the cross axis in the focused state.
  • THIN — constant compact thickness in both states.
SliderBarOrientation
  • HORIZONTAL — the bar spans the full width of its parent, the fill grows from the left edge, and thickness is the height.
  • VERTICAL — the bar stretches to the parent’s height, the fill grows from the bottom, and thickness is the width.

Behavior

  • Clamping.value is clamped into the range before it is drawn and before it is announced, so an out-of-range value never overflows the track.
  • Degenerate range. When minimumValue and maximumValue are equal the bar renders completely filled rather than dividing by zero.
  • Display-only by default. With no onChange, the bar reports role="progressbar", has no tab stop, and ignores all keys.
  • Interactive mode. With onChange and not disabled, the bar reports role="slider", becomes a tab stop, and consumes ArrowLeft and ArrowRight. Right increments, Left decrements, each by incrementPercentage of the range, clamped to the bounds. Held keys repeat, which accelerates naturally. Up and Down are deliberately left alone so vertical focus navigation still works.
  • Orientation does not change the keys. Stepping is always ArrowRight to increment and ArrowLeft to decrement, including when orientation is VERTICAL. Only the drawing axis changes.
  • Still controlled. Even in interactive mode the component never mutates value. It calls onChange with the next value and re-renders from the prop.
  • No pointer adjustment. The bar is keyboard-operated only; clicking or dragging the track does not change the value, in either orientation.
  • Disabled.disabled turns interactive mode off entirely: the role falls back to progressbar, the tab stop is removed, keys are ignored, and aria-disabled is set. There is no dimmed visual treatment.
  • Colours. The track uses --uit-color-background-track. The fill uses --uit-color-control-idle in the idle state and --uit-color-control-active in the focused state.
  • Motion.animated transitions the fill along the value axis, and the colour and thickness across state changes. The thickness transition is limited to IDLE and FOCUSED changes: the mount render snaps, and a change to size, orientation, or shouldExpandOnFocus snaps rather than tweening so a layout change never looks like a value change.
  • Reduced motion. The component does not read a motion preference for you. Call usePrefersReducedMotion and leave animated off when the user has asked for less motion; every change then snaps.

Accessibility

  • Interactive: role="slider" with aria-valuemin, aria-valuemax, aria-valuenow (the clamped raw value) and aria-valuetext (the value as a rounded percentage of the range).
  • Display-only: role="progressbar" with the same range attributes.
  • The component never emits aria-orientation, and unlike the role and value attributes it does not overwrite one you pass. Supply aria-orientation="vertical" on a vertical bar so the axis is announced.
  • aria-label defaults to the percentage, which is not a name. Always pass a descriptive aria-label unless a parent component supplies the accessible name.
  • Because focus is not derived internally, an interactive bar that never gets state={SliderBarState.FOCUSED} gives no visible focus feedback. Drive state from onFocus and onBlur, as in the example above.
  • When the bar sits inside a component that already exposes slider semantics, do not add a second labelled slider node — use that component’s own value API.
  • Pass a meaningful aria-label and keep value in application state.
  • Use THIN for dense or secondary contexts and the default size where the bar is the focus target.
  • Give a vertical bar a parent with a real height, and a horizontal bar a parent that constrains its width.
  • Prefer ListItem with showSlider or IsolatedControl for a production adjustment interface, so label, focus, value, and stepping come as one pattern.

Avoid

  • Avoid adding your own onKeyDown; interactive mode already consumes Left and Right, and your handler would take the place of that stepping.
  • Avoid expecting Up and Down to adjust a vertical interactive bar — stepping is Left and Right in both orientations.
  • Avoid expecting a click or drag on the track to seek; wire keyboard stepping or use Scrubber when pointer seeking matters.
  • Use the component-owned track and fill. Generated class names are not supported customization surfaces.
  • Avoid using a SliderBar where media time semantics matter — use Scrubber.
  • Avoid using VolumeIndicator or ZoomIndicator as an input; those are transient readouts.