๐ŸŸข Switch


Basic

use NSwitch to create a switch input.

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

<template>
<NSwitch
  v-model="enabled"
/>
</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="enabled"
  />
</NFormGroup>
</template>

Outset

By default, the switch is in inset mode. You can change it to outset mode using the outset prop.

outset - force the switch to be outset mode.

<template>
<div flex items-center space-x-4>
  <NSwitch />

  <NSwitch outset />
</div>
</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 flex="~ wrap" items-center gap-4>
  <NSwitch
    :model-value="true"
    outset
    switch="primary"
  />

  <NSwitch
    :model-value="true"
    switch="green"
  />

  <NSwitch
    :model-value="true"
    outset
    switch="info"
  />

  <NSwitch
    :model-value="true"
    switch="pink"
  />

  <NSwitch
    :model-value="true"
    outset
    switch="purple"
  />

  <NSwitch
    :model-value="true"
    switch="warning"
  />
</div>
</template>

Focus

switch="focus" - add a focus color to the switch.

<template>
<div flex items-center gap-4>
  <NSwitch
    :model-value="true"
    outset
    switch="primary focus"
  />

  <NSwitch
    :model-value="true"
    switch="green focus"
  />

  <NSwitch
    :model-value="true"
    outset
    switch="info focus"
  />

  <NSwitch
    :model-value="true"
    switch="pink focus"
  />

  <NSwitch
    :model-value="true"
    outset
    switch="purple focus"
  />

  <NSwitch
    :model-value="true"
    switch="warning focus"
  />
</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 flex="~ wrap" items-center gap-4>
  <NSwitch
    size="xs"
  />

  <NSwitch
    :model-value="true"
    switch="xl"
  />

  <NSwitch
    :model-value="true"
    size="3vw"
    outset
  />

  <NSwitch
    size="1cm"
  />

  <NSwitch
    size="45px"
    outset
  />
</div>
</template>

Icon

icon - add an icon to the switch.

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

<template>
<NSwitch
  v-model="enabled"
  off-icon="i-heroicons-sun-20-solid"
  on-icon="i-heroicons-moon-20-solid"
/>
</template>

Disabled

disabled - disable the switch.

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

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

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

Loading

loading - set the switch to loading state.

<template>
<div flex="~ wrap" items-center gap-4>
  <NSwitch
    :model-value="true"
    outset
    loading
  />

  <NSwitch
    :model-value="false"
    switch="green"
    loading
  />

  <NSwitch
    :model-value="false"
    outset
    size="lg"
    loading
    switch="info"
  />

  <NSwitch
    :model-value="true"
    loading
    size="7"
    switch="pink"
  />

  <NSwitch
    :model-value="true"
    outset
    loading
    size="8"
    switch="purple"
  />

  <NSwitch
    :model-value="true"
    loading
    switch="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 flex="~ wrap" gap-7 py-24>
  <NSwitch
    v-model="enabled"
    switch="focus"
    rotate-90
  />

  <NSwitch
    v-model="enabled"
    switch="focus rose"
    class="w-4.5em rotate-90"
    :una="{
      switchTrack: 'w-4.5em',
      switchThumbOn: 'translate-x-3em',
      switchThumbOff: 'translate-x-0',
    }"
  />

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

  <NSwitch
    v-model="enabled"
    size="3xl"
    outset
    class="rounded-non rotate-90"
    :una="{
      switchThumb: 'rounded-none h-2em w-2em',
      switchTrack: 'rounded-none',
      switchThumbOn: 'bg-success',
      switchThumbOff: 'bg-error',
    }"
  />

  <NSwitch
    v-model="enabled"
    size="4xl"
    :una="{
      switchTrackOn: 'bg-yellow',
      switchTrackOff: 'bg-blue',
      switchThumbOn: 'rounded-l-lg',
      switchThumbOff: 'rounded-r-lg',
    }"
  />
</div>
</template>

Slots

You can use the following slots to customize the switch.

NameDescriptionProps
iconThe on and off icons of the switch.on
loading-iconThe loading icon slot.on

Props

export interface NSwitchProps {
  /**
   * Value of the switch.
   *
   * @default null
   */
  modelValue?: boolean
  /**
   * Disable the switch from being clicked.
   *
   * @default false
   */
  disabled?: boolean
  /**
   * Add a loading indicator to the switch.
   * This will also disable the switch.
   *
   * @default false
   */
  loading?: boolean
  /**
   * Display the slider thumb outside of the track.
   *
   * @default false
   */
  outset?: boolean

  /**
   * 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="xl green focus"
   */
  switch?: string
  /**
   * Allows you to display an icon when the switch is on.
   * Accepts icon name and utility classes
   *
   * @example
   * icon="i-heroicons-check-20-solid text-white"
   */
  onIcon?: string
  /**
   * Allows you to display an icon when the switch is off.
   * Accepts icon name and utility classes
   *
   * @example
   * icon="i-heroicons-x-mark-20-solid text-white"
   */
  offIcon?: string

  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/switch.ts
   */
  una?: {
    switchThumb?: string
    switchThumbOn?: string
    switchThumbOff?: string

    switchTrack?: string
    switchTrackOn?: string
    switchTrackOff?: string

    switchIconBase?: string
    switchIconOn?: string
    switchIconOff?: string

    switchLoading?: string
    switchloadingicon?: string
  }
}

Presets

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

type SwitchPrefix = 'switch'

export const staticSwitch: Record<`${SwitchPrefix}-${string}` | SwitchPrefix, string> = {
  // base
  'switch': 'switch-primary bg-transparent relative inline-flex items-center justify-center flex-shrink-0 cursor-pointer rounded-full',
  'switch-disabled': 'n-disabled',
  'switch-focus': 'focus:outline-none focus:ring-2 focus:ring-offset-2 ring-offset-base focus:ring-brand',

  // inset
  'switch-inset': 'h-1.5em w-2.75em',
  'switch-track-inset': 'h-1.5em w-2.75em',

  // outset
  'switch-outset': 'h-1.25em w-2.5em',
  'switch-track-outset': 'h-1em w-2.25em',

  // thumb
  'switch-thumb': 'flex items-center justify-center h-1.25em w-1.25em absolute bg-base pointer-events-none inline-block transform rounded-full shadow transition-base',
  'switch-thumb-on': 'translate-x-1.25em',
  'switch-thumb-off': 'translate-x-0',

  // track
  'switch-track': 'pointer-events-none absolute mx-auto rounded-full transition-base',
  'switch-track-on': 'bg-brand',
  'switch-track-off': 'bg-$c-gray-200',

  // icon
  'switch-icon-base': 'text-0.8em',
  'switch-icon-off': 'text-muted',
  'switch-icon-on': 'text-muted',

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

export const dynamicSwitch = [
  [/^switch-(.*)$/, ([, 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 _switch = [
  ...dynamicSwitch,
  staticSwitch,
]

Component

<script setup lang="ts">
import { Switch } from '@headlessui/vue'
import { computed } from 'vue'
import { useVModel } from '@vueuse/core'
import type { NSwitchProps } from '../../types'
import NIcon from '../elements/Icon.vue'

defineOptions({
  inheritAttrs: false,
})

const props = defineProps<NSwitchProps>()

const emit = defineEmits<{ (...args: any): void }>()

const on = useVModel(props, 'modelValue', emit, { passive: true })

const _switch = computed(() => props.switch)

const outsetClassVariants = computed(() => {
  const switchWrapper = {
    false: 'switch-inset',
    true: 'switch-outset',
  }

  const switchTrack = {
    false: 'switch-track-inset',
    true: 'switch-track-outset',
  }

  const switchThumb = {
    false: 'left-0.125em',
    true: 'left-0 border-base border',
  }

  return {
    switchWrapper: switchWrapper[!props.outset ? 'false' : 'true'],
    switchTrack: switchTrack[!props.outset ? 'false' : 'true'],
    switchThumb: switchThumb[!props.outset ? 'false' : 'true'],
  }
})

const onClassVariants = computed(() => {
  const switchTrack = {
    true: `${props.una?.switchTrackOn ?? ''} switch-track-on`,
    false: `${props.una?.switchTrackOff ?? ''} switch-track-off`,
  }

  const switchThumb = {
    true: `${props.una?.switchThumbOn ?? ''} switch-thumb-on`,
    false: `${props.una?.switchThumbOff ?? ''} switch-thumb-off`,
  }

  const switchIcon = {
    true: `${props.onIcon ?? ''} switch-icon-on`,
    false: `${props.offIcon ?? ''} switch-icon-off`,
  }

  return {
    switchTrack: switchTrack[on.value ? 'true' : 'false'],
    switchThumb: switchThumb[on.value ? 'true' : 'false'],
    switchIcon: switchIcon[on.value ? 'true' : 'false'],
  }
})
</script>

<template>
  <Switch
    v-model="on"
    class="switch"
    :class="[
      { 'switch-disabled': disabled || loading },
      outsetClassVariants?.switchWrapper,
    ]"
    :switch="_switch"
    v-bind="$attrs"
    :disabled="disabled"
  >
    <span class="sr-only">Track</span>
    <span
      aria-hidden="true"
      switch="track"
      :class="[
        una?.switchTrack,
        onClassVariants?.switchTrack,
        outsetClassVariants?.switchTrack,
      ]"
    />

    <span class="sr-only">Thumb</span>
    <span
      aria-hidden="true"
      switch="thumb"
      :class="[
        una?.switchThumb,
        onClassVariants?.switchThumb,
        outsetClassVariants?.switchThumb,
      ]"
    >
      <span class="sr-only">Icon</span>
      <slot v-if="!loading" name="icon" :on="on">
        <NIcon
          switch="icon-base"
          :name="onClassVariants?.switchIcon"
          :class="una?.switchIconBase"
        />
      </slot>
      <slot v-else name="loading-icon" :on="on">
        <NIcon
          switch="loading"
          :class="una?.switchLoading"
          :name="una?.switchloadingicon ?? 'switch-loading-icon'"
        />
      </slot>
    </span>
  </Switch>
</template>