๐ŸŸข Slider

Features

  • Can be controlled or uncontrolled.
  • Supports multiple thumbs.
  • Supports a minimum value between thumbs.
  • Supports touch or click on track to update value.
  • Supports Right to Left direction.
  • Full keyboard navigation.
  • Customizable.

Basic

use NSlider to create a range input.

<script setup lang="ts">
const value = ref([40])

// eg. for multiple values
const multipleValue = ref([40, 60, 150])
</script>

<template>
  <div grid="~ cols-1" class="gap-8 py-4">
    <NSlider
      v-model="value"
      :min="0"
      :max="100"
    />

    <NSlider
      v-model="multipleValue"
      :min="0"
      :max="150"
    />
  </div>
</template>

Color

slider="{color}" - change the color of the slider.

You can use any color provided by the Tailwind CSS color palette, the default is primary. You can also add your own colors to the palette through the Configuration section.
<script setup lang="ts">
const models = ref([
  [30],
  [40],
  [23],
  [49.40, 70],
  [10, 50],
  [29.40, 70],
  [90],
  [100],
  [50],
])
</script>

<template>
  <div flex="~ wrap" items-center gap-4>
    <NSlider
      v-model="models[0]"
    />

    <NSlider
      v-model="models[1]"
      slider="lime"
    />

    <NSlider
      v-model="models[2]"
      slider="orange"
    />

    <NSlider
      v-model="models[3]"
      slider="pink"
    />

    <NSlider
      v-model="models[4]"
      slider="purple"
    />

    <NSlider
      v-model="models[5]"
      slider="error"
    />

    <NSlider
      v-model="models[6]"
      slider="info"
    />

    <NSlider
      v-model="models[7]"
      slider="teal"
    />
  </div>
</template>

Steps

step="{number}" - define the step value of the slider.

min-steps-between-thumbs="{number}" - define the minimum steps between thumbs.

To avoid the thumb from overlapping, you can set the min-steps-between-thumbs prop.

40

[ 40, 55, 75 ]

<script setup lang="ts">
const value = ref([40])

const multipleValue = ref([40, 55, 75])
</script>

<template>
  <div grid="~ cols-1" class="gap-8 py-4">
    <NFormGroup
      label="Step size 10"
      :message="value.toString()"
    >
      <NSlider
        v-model="value"
        :step="10"
      />
    </NFormGroup>

    <NFormGroup
      label="Multiple Values"
      :message="multipleValue"
    >
      <NSlider
        v-model="multipleValue"
        :step="10"
        :min-steps-between-thumbs="1"
      />
    </NFormGroup>
  </div>
</template>

Label

You can use the NFormGroup component to create a label for the slider.

Read more about the NFormGroup component here.
Adjust the volume
40/50
<script setup lang="ts">
const value = ref([40])

const isTooHigh = computed(() => value.value[0] > 50)
</script>

<template>
 <NFormGroup
   label="Volume"
   hint="Adjust the volume"
   :message="isTooHigh ? 'Volume is too high' : ''"
   :status="isTooHigh ? 'error' : undefined"
   :counter="{
     value: value[0],
     max: 50,
   }"
 >
   <NSlider
     v-model="value"
     :min="0"
     :max="100"
   />
 </NFormGroup>
</template>

Orientation

orientation={value} - change the orientation of the slider.

ValueDescription
horizontalThe default orientation.
verticalThe vertical orientation.
<script setup lang="ts">
const value = ref([40])
</script>

<template>
  <div>
    <NFormGroup
      class="h-50"
    >
      <NSlider
        v-model="value"
        orientation="vertical"
      />
    </NFormGroup>
    <NFormGroup
      class="w-50"
    >
      <NSlider
        v-model="value"
        orientation="horizontal"
      />
    </NFormGroup>
  </div>
</template>

Disabled

disabled - disable the slider.

<script setup lang="ts">
const value = ref([80])
const isDisabled = ref(true)
</script>

<template>
  <div class="flex flex-col space-y-4">
    <NCheckbox
      v-model="isDisabled"
      label="Disable"
    />

    <NSlider
      v-model="value"
      :disabled="isDisabled"
    />
  </div>
</template>

Size

size="{size}" - change the size of the slider.

๐Ÿš€ You can freely adjust the size of the slider using any size imaginable. No limits exist, and you can use breakpoints such as sm:sm, xs:lg to change size based on screen size or states such as hover:lg, focus:3xl to change size based on input state and more.

The height and width of the slider scale depends on the slider-size. If you want to change the height and width simultaneously, you can always customize it using utility classes.
<script setup lang="ts">
const models = ref([
  [30],
  [40],
  [23],
  [49.40, 70],
  [10, 50],
])
</script>

<template>
  <div flex="~ wrap" items-center gap-8>
    <NSlider
      v-model="models[0]"
      size="xs"
    />

    <NSlider
      v-model="models[1]"
      switch="xl"
    />

    <NSlider
      v-model="models[2]"
      size="3vw"
      outset
    />

    <NSlider
      v-model="models[3]"
      size="1cm"
    />

    <NSlider
      v-model="models[4]"
      size="45px"
    />
  </div>
</template>

Customization

You can customize the slider using the una prop and utility classes.

You can also globally customize the slider preset if you want to have a different default style. See Configuration section for more details.
<script setup lang="ts">
const value = ref([40])
</script>

<template>
<div grid="~ cols-1" class="gap-8 py-4">
  <NSlider
    v-model="value"
    slider="orange"
    :una="{
      sliderTrack: 'h-5',
      sliderThumb: 'border-3 rounded-none',
    }"
  />

  <NSlider
    v-model="value"
    slider="violet"
    :una="{
      sliderTrack: 'h-1',
      sliderThumb: 'border-3 h-8 rounded-lg',
    }"
  />
</div>
</template>

Slots

You can use the following slots to customize the slider.

NameDescriptionProps
slider-trackThe track of the slider.-
slider-thumbThe the thumb of the slider.-
slider-rangeThe the range of the slider.-

Props

import type { SliderRootProps } from 'radix-vue'
import type { HTMLAttributes } from 'vue'

type Extensions = SliderRootProps & { class?: HTMLAttributes['class'] }

export interface NSliderProps extends Extensions {
  /**
   * Allows you to add `UnaUI` slider preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/slider.ts
   * @example
   * slider="green""
   */
  slider?: string
  /**
   * Allows you to change the size of the slider.
   *
   * @default base|md
   *
   * @example
   * size="sm" | size="2cm" | size="2rem" | size="2px"
   */
  size?: string
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/slider.ts
   */
  una?: {
    // components
    sliderRoot?: HTMLAttributes['class']
    sliderTrack?: HTMLAttributes['class']
    sliderRange?: HTMLAttributes['class']
    sliderThumb?: HTMLAttributes['class']
  }
}

Presets

import { parseColor } from '@unocss/preset-mini/utils'
import type { Theme } from '@unocss/preset-uno'
import type { RuleContext } from '@unocss/core'

type SliderPrefix = 'slider'

export const staticSlider: Record<`${SliderPrefix}-${string}` | SliderPrefix, string> = {
  // configurations
  'slider': 'slider-primary size-base',
  'slider-disabled': 'n-disabled',
  'slider-root-vertical': 'flex-col w-1em h-full',
  'slider-track-vertical': 'w-0.5em h-full',
  'slider-range-vertical': 'w-full',
  'slider-thumb-vertical': 'w-1.25em h-1.25em',

  // components
  'slider-root': 'slider relative flex w-full touch-none select-none items-center',
  'slider-track': 'h-0.5em relative w-full grow overflow-hidden rounded-full bg-muted',
  'slider-range': 'absolute h-full bg-brand',
  'slider-thumb': 'w-1.25em h-1.25em block border-2 border-brand rounded-full bg-base focus-visible:ring-base ring-offset-base transition-colors disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
}

export const dynamicSlider = [
  [/^slider-(.*)$/, ([, body]: string[], { theme }: RuleContext<Theme>) => {
    const color = parseColor(body, theme)
    if ((color?.cssColor?.type === 'rgb' || color?.cssColor?.type === 'rgba') && color.cssColor.components)
      return `n-${body}-600 dark:n-${body}-500`
  }],
]

export const slider = [
  ...dynamicSlider,
  staticSlider,
]

Component

<script setup lang="ts">
import { computed } from 'vue'
import type { SliderRootEmits } from 'radix-vue'
import { SliderRange, SliderRoot, SliderThumb, SliderTrack, useForwardPropsEmits } from 'radix-vue'
import type { NSliderProps } from '../../types'
import { cn } from '../../utils'

const props = defineProps<NSliderProps>()
const emits = defineEmits<SliderRootEmits>()
const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const isVertical = computed(() => props.orientation === 'vertical')
</script>

<template>
  <SliderRoot
    :class="cn(
      'slider-root',
      isVertical && 'slider-root-vertical',
      props.class,
      props.una?.sliderRoot,
      disabled && 'slider-disabled',
    )"
    v-bind="forwarded"
  >
    <slot name="slider-track">
      <SliderTrack
        :class="cn(
          'slider-track',
          isVertical && 'slider-track-vertical',
          props.una?.sliderTrack,
        )"
      >
        <slot name="slider-range">
          <SliderRange
            :class="cn(
              'slider-range',
              isVertical && 'slider-range-vertical',
              props.una?.sliderRange,
            )"
          />
        </slot>
      </SliderTrack>
    </slot>

    <slot name="slider-thumb">
      <SliderThumb
        v-for="(_, key) in modelValue"
        :key="key"
        :class="cn(
          'slider-thumb',
          isVertical && 'slider-thumb-vertical',
          props.una?.sliderThumb,
        )"
      />
    </slot>
  </SliderRoot>
</template>