Skip to main content

Overview

The slider widget creates a focusable, adjustable slider for selecting numeric values within a range. It displays a track with a visual indicator showing the current position.

Basic Usage

import { ui } from "@rezi-ui/core";

ui.slider({
  id: "volume",
  value: state.volume,
  min: 0,
  max: 100,
  onChange: (value) => app.update({ volume: value })
})

Props

id
string
required
Unique identifier for focus routing. Required for all sliders.
value
number
required
Current slider value. Must be controlled by your application state.
min
number
default:"0"
Minimum value for the slider range.
max
number
default:"100"
Maximum value for the slider range.
step
number
default:"1"
Step increment for keyboard adjustments and value quantization.
onChange
(value: number) => void
Callback invoked when the value changes.
label
string
Optional label shown before the track.
showValue
boolean
default:"true"
Whether to display the numeric value text.
width
number
Optional fixed track width in cells. Defaults to filling available width.
disabled
boolean
default:"false"
When true, slider cannot be focused or adjusted.
readOnly
boolean
default:"false"
When true, slider is focusable but non-editable.
focusable
boolean
default:"true"
When false, opt out of Tab focus order while keeping id-based routing available.
accessibleLabel
string
Optional semantic label for accessibility and debug announcements.

Styling Props

style
TextStyle
Optional style applied to label/value text.
focusConfig
FocusConfig
Optional focus appearance configuration for custom focus indicators.

Event Handling

Sliders use a controlled pattern. You must manage the value in state:
ui.slider({
  id: "brightness",
  value: state.brightness,
  min: 0,
  max: 100,
  step: 5,
  label: "Brightness",
  onChange: (value) => {
    app.update({ brightness: value });
    
    // Apply brightness setting
    setBrightness(value);
  }
})

Value Range and Step

Integer Steps

ui.slider({
  id: "count",
  value: state.count,
  min: 1,
  max: 10,
  step: 1,
  label: "Count"
})

Decimal Steps

ui.slider({
  id: "opacity",
  value: state.opacity,
  min: 0,
  max: 1,
  step: 0.1,
  label: "Opacity"
})

Large Range

ui.slider({
  id: "timeout",
  value: state.timeout,
  min: 0,
  max: 10000,
  step: 100,
  label: "Timeout (ms)"
})

Form Integration

Sliders are typically wrapped with ui.field() for labels:
ui.form([
  ui.field({
    label: "Audio Settings",
    children: ui.column({ gap: 1 }, [
      ui.slider({
        id: "volume",
        value: state.volume,
        min: 0,
        max: 100,
        step: 5,
        label: "Volume",
        onChange: (value) => app.update({ volume: value })
      }),
      ui.slider({
        id: "bass",
        value: state.bass,
        min: -10,
        max: 10,
        step: 1,
        label: "Bass",
        onChange: (value) => app.update({ bass: value })
      }),
      ui.slider({
        id: "treble",
        value: state.treble,
        min: -10,
        max: 10,
        step: 1,
        label: "Treble",
        onChange: (value) => app.update({ treble: value })
      })
    ])
  }),
  ui.actions([
    ui.button("reset", "Reset", { intent: "secondary" }),
    ui.button("save", "Save", { intent: "primary" })
  ])
])

Visual Customization

With Label

ui.slider({
  id: "speed",
  value: state.speed,
  min: 0,
  max: 100,
  label: "Speed",
  showValue: true
})

Without Value Display

ui.slider({
  id: "position",
  value: state.position,
  min: 0,
  max: 100,
  showValue: false
})

Fixed Width

ui.slider({
  id: "volume",
  value: state.volume,
  min: 0,
  max: 100,
  width: 20,
  label: "Volume"
})

Examples

Volume Control

ui.row({ gap: 1, items: "center" }, [
  ui.icon(state.volume === 0 ? "audio.mute" : "audio.volume"),
  ui.slider({
    id: "volume",
    value: state.volume,
    min: 0,
    max: 100,
    step: 5,
    width: 20,
    onChange: (value) => {
      app.update({ volume: value });
      if (value === 0) {
        muteAudio();
      } else {
        setVolume(value);
      }
    }
  }),
  ui.text(`${state.volume}%`)
])

Range Filter

ui.panel("Price Range", [
  ui.column({ gap: 1 }, [
    ui.slider({
      id: "min-price",
      value: state.minPrice,
      min: 0,
      max: state.maxPrice,
      step: 10,
      label: "Min",
      onChange: (value) => {
        app.update({ minPrice: value });
        filterProducts();
      }
    }),
    ui.slider({
      id: "max-price",
      value: state.maxPrice,
      min: state.minPrice,
      max: 1000,
      step: 10,
      label: "Max",
      onChange: (value) => {
        app.update({ maxPrice: value });
        filterProducts();
      }
    }),
    ui.text(`$${state.minPrice} - $${state.maxPrice}`, { dim: true })
  ])
])

Animation Speed

ui.field({
  label: "Animation Speed",
  hint: "Adjust playback speed",
  children: ui.slider({
    id: "speed",
    value: state.animationSpeed,
    min: 0.25,
    max: 2,
    step: 0.25,
    showValue: true,
    onChange: (value) => {
      app.update({ animationSpeed: value });
      updateAnimationSpeed(value);
    }
  })
})

Progress Seeker

ui.column({ gap: 0 }, [
  ui.slider({
    id: "progress",
    value: state.currentTime,
    min: 0,
    max: state.duration,
    step: 1,
    showValue: false,
    onChange: (value) => {
      app.update({ currentTime: value });
      seekTo(value);
    }
  }),
  ui.row({ justify: "between" }, [
    ui.text(formatTime(state.currentTime), { dim: true }),
    ui.text(formatTime(state.duration), { dim: true })
  ])
])

Settings Panel

ui.panel("Display Settings", [
  ui.column({ gap: 1 }, [
    ui.slider({
      id: "brightness",
      value: state.brightness,
      min: 0,
      max: 100,
      step: 5,
      label: "๐Ÿ”† Brightness",
      onChange: (value) => app.update({ brightness: value })
    }),
    ui.slider({
      id: "contrast",
      value: state.contrast,
      min: 0,
      max: 100,
      step: 5,
      label: "๐ŸŒ— Contrast",
      onChange: (value) => app.update({ contrast: value })
    }),
    ui.slider({
      id: "saturation",
      value: state.saturation,
      min: 0,
      max: 100,
      step: 5,
      label: "๐ŸŽจ Saturation",
      onChange: (value) => app.update({ saturation: value })
    }),
    ui.divider(),
    ui.actions([
      ui.button("reset", "Reset to Defaults", {
        intent: "secondary",
        onPress: () => app.update({
          brightness: 50,
          contrast: 50,
          saturation: 50
        })
      })
    ])
  ])
])

Keyboard Shortcuts

When focused, sliders support:
  • Arrow Left/Down - Decrease by one step
  • Arrow Right/Up - Increase by one step
  • Page Down - Decrease by 10 steps
  • Page Up - Increase by 10 steps
  • Home - Jump to minimum value
  • End - Jump to maximum value
  • Tab - Move to next focusable element
  • Shift+Tab - Move to previous focusable element

Value Formatting

Values are automatically formatted based on the step precision:
// Integer step: displays "50"
ui.slider({ id: "int", value: 50, step: 1 })

// Decimal step: displays "0.5"
ui.slider({ id: "dec", value: 0.5, step: 0.1 })

// High precision: displays "3.14"
ui.slider({ id: "pi", value: 3.14, step: 0.01 })
Trailing zeros are removed for cleaner display.

Read-Only State

ui.slider({
  id: "progress",
  value: state.downloadProgress,
  min: 0,
  max: 100,
  readOnly: true,
  label: "Download Progress"
})
Read-only sliders can be focused but not adjusted by the user.

Accessibility

  • Sliders require a unique id for focus routing
  • Use accessibleLabel for descriptive announcements
  • Use label prop to describe the sliderโ€™s purpose
  • Wrap with ui.field() for additional context
  • Disabled sliders are not focusable or adjustable
  • Read-only sliders are focusable but not adjustable
  • Focus indicators are shown when navigating with keyboard
  • Keyboard navigation uses intuitive arrow keys
  • Input - For text-based numeric input
  • Field - For wrapping inputs with labels
  • Progress - For displaying progress (non-interactive)
  • Select - For discrete option selection

Build docs developers (and LLMs) love