RangeInput

A slider: one value on a range, or two or more thumbs for a range of values.

A slider: one value on a range, or two or more thumbs for a range of values. Each thumb is a visually hidden native <input type="range">, so the keys (the arrows, Page, Home and End), assistive technology and forms work as the platform's; the pointer drags a thumb, touch included.

Every name is yours - each thumb's (getAriaLabel, or a FormLabel), and the text of its value (getAriaValueText) - so the slider carries no language of its own.

import { RangeInput } from '@soroush.tech/design-system/RangeInput'

// One thumb
;<RangeInput
  aria-label="Volume"
  value={volume}
  onChange={(_event, value) => setVolume(value as number)}
/>

// A range - one thumb for each value
;<RangeInput
  value={[20, 70]}
  onChange={(_event, value) => setRange(value as number[])}
  getAriaLabel={(index) => (index === 0 ? 'Least price' : 'Greatest price')}
  valueLabel="auto"
/>

Examples

Default

const args: Partial<RangeInputProps> = { defaultValue: 40, 'aria-label': 'Value' }

<RangeInput key={JSON.stringify(args.defaultValue)} {...args} />

Range

const args: Partial<RangeInputProps> = { defaultValue: [20, 70], valueLabel: 'auto' }

<RangeInput key={JSON.stringify(args.defaultValue)} getAriaLabel={priceLabel} {...args} />

Controlled Value

Value: 30
const args: Partial<RangeInputProps> = { 'aria-label': 'Value', defaultValue: 40 }

<Controlled {...args} />

Marks

const args: Partial<RangeInputProps> = {
  defaultValue: 30,
  min: 10,
  max: 110,
  step: 10,
  marks: true,
  'aria-label': 'Value',
}

<RangeInput key={JSON.stringify(args.defaultValue)} {...args} />

Custom Marks

const args: Partial<RangeInputProps> = { 'aria-label': 'Value', defaultValue: 40 }

<RangeInput {...args} defaultValue={20} step={10} marks={labelled} />

Restricted To Marks

const args: Partial<RangeInputProps> = { 'aria-label': 'Value', defaultValue: 40 }

<RangeInput {...args} defaultValue={20} step={null} marks={labelled} />

Value Label Always Shown

const args: Partial<RangeInputProps> = { defaultValue: 40, valueLabel: 'on', 'aria-label': 'Value' }

<View paddingTop={4}>
  <RangeInput key={JSON.stringify(args.defaultValue)} {...args} />
</View>

Vertical

const args: Partial<RangeInputProps> = {
  defaultValue: 40,
  step: 20,
  marks: true,
  orientation: 'vertical',
  'aria-label': 'Value',
}

<RangeInput key={JSON.stringify(args.defaultValue)} {...args} />

Without Track

const args: Partial<RangeInputProps> = { defaultValue: [20, 37, 50], track: false, 'aria-label': 'Value' }

<RangeInput {...args} getAriaLabel={(index) => `Thumb ${index + 1}`} />

Inverted Track

const args: Partial<RangeInputProps> = { defaultValue: [30, 60], track: 'inverted' }

<RangeInput {...args} getAriaLabel={priceLabel} />

Minimum Distance Between Thumbs

const args: Partial<RangeInputProps> = { 'aria-label': 'Value', defaultValue: 40 }

<MinimumDistance {...args} />

Non Linear Scale

const args: Partial<RangeInputProps> = {
  defaultValue: 4,
  min: 0,
  max: 10,
  step: 1,
  valueLabel: 'auto',
  'aria-label': 'Value',
}

<View paddingTop={4}>
  <RangeInput
    key={JSON.stringify(args.defaultValue)}
    {...args}
    scale={(value) => 2 ** value}
    getAriaValueText={(value) => `${value} bytes`}
  />
</View>

With Aria Value Text

const args: Partial<RangeInputProps> = {
  defaultValue: 30,
  min: 0,
  max: 60,
  step: 5,
  shiftStep: 15,
  valueLabel: 'auto',
  'aria-label': 'Timer',
}

<View paddingTop={4}>
  <RangeInput
    key={JSON.stringify(args.defaultValue)}
    {...args}
    getAriaValueText={minutes}
    formatValueLabel={minutes}
  />
</View>

Range With Aria Value Text

const args: Partial<RangeInputProps> = { defaultValue: [9, 17], min: 0, max: 24, valueLabel: 'auto' }

<View paddingTop={4}>
  <RangeInput
    {...args}
    getAriaLabel={(index) => (index === 0 ? 'Opens at' : 'Closes at')}
    getAriaValueText={(value) => `${value}:00`}
    formatValueLabel={(value) => `${value}:00`}
  />
</View>

With An Input Field

const args: Partial<RangeInputProps> = { 'aria-label': 'Value', defaultValue: 40 }

<WithNumberInput {...args} />

Sizes

const args: Partial<RangeInputProps> = { 'aria-label': 'Value', defaultValue: 40 }

<Flex gap={2}>
  {(['sm', 'md', 'lg'] as const).map((size) => (
    <RangeInput key={size} {...args} size={size} aria-label={`Value, ${size}`} />
  ))}
</Flex>

Colors

const args: Partial<RangeInputProps> = { 'aria-label': 'Value', defaultValue: 40 }

<Flex gap={2}>
  {(['primary', 'secondary', 'success', 'error'] as const).map((color) => (
    <RangeInput key={color} {...args} color={color} aria-label={`Value, ${color}`} />
  ))}
</Flex>

States

const args: Partial<RangeInputProps> = { 'aria-label': 'Value', defaultValue: 40 }

<Flex gap={2}>
  <RangeInput {...args} error aria-label="Value, in error" />
  <RangeInput {...args} disabled aria-label="Value, disabled" />
</Flex>

In Form Control

Loud or quiet.

const { error, disabled, size, color }: Partial<RangeInputProps> = { 'aria-label': 'Value', defaultValue: 40 }

<FormControl error={error} disabled={disabled} size={size} color={color}>
  <FormLabel>Volume</FormLabel>
  <RangeInput defaultValue={30} valueLabel="auto" />
  <FormHelperText>{error ? 'Too loud.' : 'Loud or quiet.'}</FormHelperText>
</FormControl>

Right To Left

const args: Partial<RangeInputProps> = { defaultValue: 30, valueLabel: 'auto', 'aria-label': 'Value' }

<View dir="rtl" paddingTop={4}>
  <RangeInput {...args} />
</View>

Customized

<ThemeProvider theme={customized}>
  <View paddingTop={4}>
    <RangeInput aria-label="Value" defaultValue={40} />
  </View>
</ThemeProvider>