---
name: toggle
title: Toggle
url: https://ui.frontic.com/components/ui/toggle.md
tier: ui
install: npx @frontic/ui add toggle
files:
  - components/ui/toggle/Toggle.vue
  - components/ui/toggle/index.ts
dependencies:
  - "@vueuse/core"
  - class-variance-authority
  - reka-ui
---

# Toggle

A button that stays pressed until pressed again, such as saving to a wishlist; plain or outlined, with square icon sizes.

## Install

```sh
npx @frontic/ui add toggle
```

Used by: [Toggle Group](https://ui.frontic.com/components/ui/toggle-group.md)

npm packages: `@vueuse/core`, `class-variance-authority`, `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { HeartIcon } from '@lucide/vue'
import { Toggle } from '@/components/ui/toggle'
</script>

<template>
  <Toggle v-slot="{ pressed }" aria-label="Save to wishlist" variant="outline" size="icon-md">
    <HeartIcon data-icon :class="pressed ? 'fill-current' : undefined" />
  </Toggle>
</template>
```

## API reference

### Toggle

Lets the user switch an option on or off with a button. Renders a `<button>`.

`data-slot="toggle"` · `Toggle.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultValue` | `boolean` | `false` | The pressed state of the toggle when it is initially rendered. Use when you do not need to control its open state. |
| `modelValue` | `boolean \| null`: `null \| boolean` |  | The controlled pressed state of the toggle. Can be bind as `v-model`. |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with the toggle. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `name` | `string` |  | The name of the field. Submitted with its owning form as part of a name/value pair. |
| `required` | `boolean` | `false` | When `true`, indicates that the user must set the value before the owning form can be submitted. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `variant` | `'default' \| 'outline'` | `'default'` | The visual style: transparent or outlined. |
| `size` | `'md' \| 'sm' \| 'lg' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The height and padding of the toggle; the icon sizes make a square toggle that holds only an icon. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `value: boolean` | Event handler called when the value of the toggle changes. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ modelValue: boolean; state: DataState; pressed: boolean; disabled: boolean; }` | Holds the toggle's label or icon; gets whether it is pressed or disabled and its state as `on` or `off`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-state` | `'on' \| 'off'` | The state the part is in, one value per state. |

## Exports

```ts
import { type ToggleVariant, type ToggleSize } from '@/components/ui/toggle'
```

- `ToggleVariant`: `'default' \| 'outline'`
- `ToggleSize`: `'md' \| 'sm' \| 'lg' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'`

## Styling

| Part | Recipe |
| --- | --- |
| `default` | [`toggle`](https://ui.frontic.com/DESIGN.md#toggle) |
| `focus-ring` | [`focus-ring`](https://ui.frontic.com/DESIGN.md#focus-ring) |
| `disabled` | [`disabled`](https://ui.frontic.com/DESIGN.md#disabled) |

States with a look of their own: hover, focus, checked, disabled and invalid.

## Source

`components/ui/toggle/Toggle.vue`

```vue
<script lang="ts">
/** Lets the user switch an option on or off with a button. */
export default {}
</script>

<script setup lang="ts">
import type { ToggleEmits, ToggleProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { cva, type VariantProps } from 'class-variance-authority'
import { Toggle, useForwardPropsEmits } from 'reka-ui'

const toggle = cva(
  'fui-toggle group/toggle whitespace-nowrap outline-none transition-colors focus-visible:fui-focus-ring disabled:pointer-events-none disabled:fui-disabled **:data-icon:pointer-events-none **:data-icon:shrink-0',
  {
    variants: {
      variant: { default: 'fui-toggle-default', outline: 'fui-toggle-outline' },
      size: { md: 'fui-toggle-md', sm: 'fui-toggle-sm', lg: 'fui-toggle-lg', 'icon-sm': 'fui-toggle-icon-sm', 'icon-md': 'fui-toggle-icon-md', 'icon-lg': 'fui-toggle-icon-lg' },
    },
    defaultVariants: { variant: 'default', size: 'md' },
  },
)

const props = withDefaults(
  defineProps<
    ToggleProps & {
      class?: HTMLAttributes['class']
      /** The visual style: transparent or outlined. */
      variant?: VariantProps<typeof toggle>['variant']
      /** The height and padding of the toggle; the icon sizes make a square toggle that holds only an icon. */
      size?: VariantProps<typeof toggle>['size']
    }
  >(),
  {
    variant: 'default',
    size: 'md',
    disabled: false,
  },
)

const emits = defineEmits<ToggleEmits>()

const delegatedProps = reactiveOmit(props, 'class', 'size', 'variant')
const forwarded = useForwardPropsEmits(delegatedProps, emits)

type DataState = 'on' | 'off'

defineSlots<{
  /** Holds the toggle's label or icon; gets whether it is pressed or disabled and its state as `on` or `off`. */
  default?: (props: { modelValue: boolean; state: DataState; pressed: boolean; disabled: boolean }) => unknown
}>()
</script>

<template>
  <Toggle v-slot="slotProps" data-slot="toggle" v-bind="forwarded" :class="[toggle({ variant, size }), props.class]">
    <slot v-bind="slotProps" />
  </Toggle>
</template>
```

`components/ui/toggle/index.ts`

```ts
export { default as Toggle } from './Toggle.vue'

export type ToggleVariant = 'default' | 'outline'
export type ToggleSize = 'md' | 'sm' | 'lg' | 'icon-sm' | 'icon-md' | 'icon-lg'
```
