๐ŸŸข Switch

  • Full keyboard navigation.
  • Can be controlled or uncontrolled.

Basic

NSwitch is used to create a switch input. For more information about the APIs, please refer to the Radix-UI Switch documentation.

PropsTypeDefaultDescription
checkedboolean-The controlled state of the switch. Can be bind as v-model:checked.
defaultCheckedboolean-The uncontrolled state of the switch.
valuestringonThe value of the switch.
<script setup lang="ts">
const enabled = ref(false)
</script>

<template>
  <NSwitch
    v-model:checked="enabled"
    switch-selected="red"
  />
</template>

Label

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

Notice that when you click on the label, the switch gets toggled. Read more about the NFormGroup component here.
<script setup lang="ts">
const enabled = ref(false)
</script>

<template>
  <NFormGroup :label="enabled ? 'Toggled on' : 'Toggled off'">
    <NSwitch
      v-model:checked="enabled"
    />
  </NFormGroup>
</template>

Color

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

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.
<template>
  <div class="flex flex-wrap items-center gap-4">
    <NSwitch
      default-checked
      switch-checked="primary"
    />

    <NSwitch
      default-checked
      switch-checked="green"
    />

    <NSwitch
      default-checked
      outset
      switch-checked="info"
    />

    <NSwitch
      default-checked
      switch-checked="pink"
    />

    <NSwitch
      default-checked
      outset
      switch-checked="purple"
    />

    <NSwitch
      default-checked
      switch-checked="warning"
    />
  </div>
</template>

Size

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

๐Ÿš€ You can freely adjust the size of the switch 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 switch scale depends on the switch-size. If you want to change the height and width simultaneously, you can always customize it using utility classes.
<template>
  <div class="flex flex-wrap items-center gap-4">
    <NSwitch
      size="xs"
    />

    <NSwitch
      size="lg"
    />

    <NSwitch
      size="xl"
    />

    <NSwitch
      size="2xl"
    />

    <NSwitch
      size="3xl"
    />
  </div>
</template>

Icon

icon - add an icon to the switch.

checkedIcon - add an icon for the on state.

uncheckedIcon - add an icon for the off state.

<script setup lang="ts">
const enabled = ref(false)
</script>

<template>
  <div class="flex gap-2">
    <NSwitch
      v-model:checked="enabled"
      size="lg"
      icon="i-heroicons-star-20-solid"
    />
    <NSwitch
      v-model:checked="enabled"
      size="lg"
      checked-icon="i-heroicons-sun-20-solid"
      unchecked-icon="i-heroicons-moon-20-solid"
    />
  </div>
</template>

Disabled

disabled - disable the switch.

<script setup lang="ts">
const enabled = ref(false)
</script>

<template>
  <div class="flex flex-col gap-4">
    <NFormGroup :label="!enabled ? 'Clickable' : 'Disabled'">
      <NSwitch
        v-model:checked="enabled"
        :disabled="enabled"
      />
    </NFormGroup>

    <NFormGroup :label="enabled ? 'Clickable' : 'Disabled'">
      <NSwitch
        v-model:checked="enabled"
        :disabled="!enabled"
      />
    </NFormGroup>
  </div>
</template>

Loading

loading - set the switch to loading state.

<template>
  <div class="flex flex-wrap items-center gap-4">
    <NSwitch
      checked
      loading
    />

    <NSwitch
      checked
      switch="green"
      loading
    />

    <NSwitch
      checked
      size="lg"
      loading
      switch-checked="info"
    />

    <NSwitch
      checked
      loading
      size="7"
      switch-checked="pink"
    />

    <NSwitch
      checked
      loading
      size="8"
      switch-checked="purple"
    />

    <NSwitch
      checked
      loading
      switch-checked="warning"
    />
  </div>
</template>

Customization

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

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

<template>
  <div class="flex flex-wrap gap-7 py-24">
    <NSwitch
      v-model:checked="enabled"
      switch-checked="primary"
      rotate-90
    />

    <NSwitch
      v-model:checked="enabled"
      switch-checked="rose"
      class="w-4.5em rotate-90"
      :una="{
        switchThumbChecked: 'translate-x-3em',
      }"
    />

    <NSwitch
      v-model:checked="enabled"
      switch-checked="indigo"
      size="1cm"
      class="rotate-120 rounded-lg"
      :una="{
        switchThumb: 'bg-inverted',
      }"
    />

    <NSwitch
      v-model:checked="enabled"
      size="3xl"
      class="rotate-90 rounded-none"
      :una="{
        switchThumb: 'rounded-none square-2em',
        switchThumbChecked: 'bg-success',
        switchThumbUnchecked: '-translate-x-5 bg-error',
      }"
    />

    <NSwitch
      v-model:checked="enabled"
      size="4xl"
      :una="{
        switchChecked: 'bg-yellow',
        switchUnchecked: 'bg-blue',
        switchThumbChecked: 'rounded-l-lg',
        switchThumbUnchecked: 'rounded-r-lg',
      }"
    />
  </div>
</template>

Slots

You can use the following slots to customize the switch.

NameDescriptionProps
iconCustomizable icons for the switch in both checked and unchecked states.checked
loading-iconThe loading icon slot.checked

Props

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

export interface NSwitchProps extends SwitchRootProps {
  /**
   * Add a loading indicator to the switch.
   * This will also disable the switch.
   *
   * @default false
   */
  loading?: boolean
  /**
   * Allows you to display an icon. Equivalent of setting both `onIcon` and `offIcon`.
   *
   * @example
   * icon="i-heroicons-question-20-solid"
   */
  icon?: HTMLAttributes['class']
  /**
   * Allows you to display an icon when the switch is on.
   * Accepts icon name and utility classes
   *
   * @example
   * on-icon="i-heroicons-check-20-solid text-white"
   */
  checkedIcon?: HTMLAttributes['class']
  /**
   * Allows you to display an icon when the switch is off.
   * Accepts icon name and utility classes
   *
   * @example
   * off-icon="i-heroicons-x-mark-20-solid text-white"
   */
  uncheckedIcon?: HTMLAttributes['class']
  /**
   * Allows you to change the size of the input.
   *
   * @default md
   *
   * @example
   * size="sm" | size="2cm" | size="2rem" | size="2px"
   */
  size?: HTMLAttributes['class']
  /**
   * Allows you to add `UnaUI` switch preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @example
   * switch-checked="green"
   */
  switchChecked?: HTMLAttributes['class']
  /**
   * Allows you to add `UnaUI` switch preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @example
   * switch-unchecked="gray"
   */
  switchUnchecked?: HTMLAttributes['class']
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/switch.ts
   */
  una?: {
    switch?: HTMLAttributes['class']
    switchChecked?: HTMLAttributes['class']
    switchUnchecked?: HTMLAttributes['class']

    switchThumb?: HTMLAttributes['class']
    switchThumbChecked?: HTMLAttributes['class']
    switchThumbUnchecked?: HTMLAttributes['class']

    switchIcon?: HTMLAttributes['class']
    switchIconChecked?: HTMLAttributes['class']
    switchIconUnchecked?: HTMLAttributes['class']

    switchLoading?: HTMLAttributes['class']
    switchLoadingIconName?: HTMLAttributes['class']
  }
}

Presets

import type { RuleContext } from 'unocss'
import { parseColor, type Theme } from '@unocss/preset-mini'

type SwitchPrefix = 'switch'

export const staticSwitch: Record<`${SwitchPrefix}-${string}` | SwitchPrefix, string> = {
  // base
  'switch': 'bg-brand switch-focus h-1.25em w-2.25em switch-disabled inline-flex shrink-0 cursor-pointer items-center border-2 border-transparent rounded-full shadow-sm',
  'switch-disabled': 'data-[disabled]:n-disabled',
  'switch-focus': 'focus-visible:ring-brand focus-visible:ring-offset-base transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',

  // thumb
  'switch-thumb': 'square-1em flex items-center justify-center absolute pointer-events-none block border border-base rounded-full bg-base shadow-lg ring-0 transition-transform',
  'switch-thumb-checked': 'translate-x-1.01em',
  'switch-thumb-unchecked': 'translate-x-0.03em',

  // icon
  'switch-icon': 'text-0.7em',
  'switch-icon-unchecked': 'text-muted',
  'switch-icon-checked': 'text-muted',

  // loading
  'switch-loading-icon': 'text-gray animate-spin text-0.8em',
  'switch-loading-icon-name': 'i-loading',
}

export const dynamicSwitch = [
  [
    /^switch-checked(?:-([^-]+))?(?:-([^-]+))?$/,
    ([, color = 'primary'], { theme }: RuleContext<Theme>) => {
      const parsedColor = parseColor(color, theme)
      if ((parsedColor?.cssColor?.type === 'rgb' || parsedColor?.cssColor?.type === 'rgba') && parsedColor.cssColor.components)
        return `data-[state=checked]:n-${color}-600 dark:data-[state=checked]:n-${color}-500`
    },
  ],

  [
    /^switch-unchecked(?:-([^-]+))?(?:-([^-]+))?$/,
    ([, color = 'primary'], { theme }: RuleContext<Theme>) => {
      const parsedColor = parseColor(color, theme)
      if ((parsedColor?.cssColor?.type === 'rgb' || parsedColor?.cssColor?.type === 'rgba') && parsedColor.cssColor.components)
        return `data-[state=unchecked]:n-${color}-200 dark:data-[state=unchecked]:n-${color}-700/58`
    },
  ],
]

export const _switch = [
  ...dynamicSwitch,
  staticSwitch,
]

Component

<script setup lang="ts">
import type { NSwitchProps } from '../../types'
import {
  SwitchRoot,
  type SwitchRootEmits,
  SwitchThumb,
  useForwardPropsEmits,
} from 'radix-vue'
import { cn } from '../../utils'
import NIcon from '../elements/Icon.vue'

const props = withDefaults(defineProps<NSwitchProps>(), {
  size: 'md',
  switchChecked: 'primary',
  switchUnchecked: 'gray',
})

const emit = defineEmits<SwitchRootEmits>()

const forwarded = useForwardPropsEmits(props, emit)
</script>

<template>
  <SwitchRoot
    v-slot="{ checked }"
    v-bind="forwarded"
    :class="cn(
      'peer switch',
      una?.switch,
      checked
        ? una?.switchChecked
        : una?.switchUnchecked,
    )"
    :disabled="disabled || loading"
    :switch-unchecked
    :switch-checked
    :size
  >
    <SwitchThumb
      :class="cn(
        'switch-thumb',
        una?.switchThumb,
        checked
          ? 'switch-thumb-checked'
          : 'switch-thumb-unchecked',
        checked
          ? una?.switchThumbChecked
          : una?.switchThumbUnchecked,
      )"
    >
      <slot v-if="!loading" name="icon" :checked>
        <NIcon
          :name="(checked ? checkedIcon : uncheckedIcon) || icon"
          :class="cn(
            'switch-icon',
            una?.switchIcon,
            checked
              ? ['switch-icon-checked', una?.switchIconChecked]
              : ['switch-icon-unchecked', una?.switchIconUnchecked],
          )"
        />
      </slot>
      <slot v-else name="loading-icon" :checked>
        <NIcon
          :class="cn(
            'switch-icon switch-loading-icon',
            una?.switchLoading,
          )"
          :name="una?.switchLoadingIconName || 'switch-loading-icon-name'"
        />
      </slot>
    </SwitchThumb>
  </SwitchRoot>
</template>