Toggle
Single press-to-latch control on bits-ui Toggle. Pressed state eases through the muted fill, and the focus ring snaps in on keyboard activation without layout shift.
Usage
toggle.svelte
<script lang="ts">
import { Toggle } from '$lib/components/ui/toggle';
import { Icon } from '$lib/components/ui/icon';
let pressed = $state(false);
</script>
<Toggle bind:pressed aria-label="Bold">
<Icon name="bold" />
</Toggle>Variants
toggle.svelte
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
export const toggleVariants = tv({
base: "hover:text-foreground aria-pressed:bg-substrate-active focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive gap-1 rounded-(--radius-control) text-sm font-medium transition-colors [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-substrate-hover inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-(length:--ring-width) disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: 'bg-transparent',
outline: 'border-input hover:bg-substrate-hover border bg-transparent'
},
size: {
default:
'h-9 min-w-9 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5',
sm: 'h-8 min-w-8 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
lg: 'h-10 min-w-10 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ToggleVariant = VariantProps<typeof toggleVariants>['variant'];
export type ToggleSize = VariantProps<typeof toggleVariants>['size'];
export type ToggleVariants = VariantProps<typeof toggleVariants>;
</script>
<script lang="ts">
import { Toggle as TogglePrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
pressed = $bindable(false),
class: className,
size = 'default',
variant = 'default',
...restProps
}: TogglePrimitive.RootProps & {
variant?: ToggleVariant;
size?: ToggleSize;
} = $props();
</script>
<TogglePrimitive.Root
bind:ref
bind:pressed
data-slot="toggle"
class={cn(toggleVariants({ variant, size }), className)}
{...restProps}
/>
Sizes
toggle.svelte
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
export const toggleVariants = tv({
base: "hover:text-foreground aria-pressed:bg-substrate-active focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive gap-1 rounded-(--radius-control) text-sm font-medium transition-colors [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-substrate-hover inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-(length:--ring-width) disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: 'bg-transparent',
outline: 'border-input hover:bg-substrate-hover border bg-transparent'
},
size: {
default:
'h-9 min-w-9 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5',
sm: 'h-8 min-w-8 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
lg: 'h-10 min-w-10 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ToggleVariant = VariantProps<typeof toggleVariants>['variant'];
export type ToggleSize = VariantProps<typeof toggleVariants>['size'];
export type ToggleVariants = VariantProps<typeof toggleVariants>;
</script>
<script lang="ts">
import { Toggle as TogglePrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
pressed = $bindable(false),
class: className,
size = 'default',
variant = 'default',
...restProps
}: TogglePrimitive.RootProps & {
variant?: ToggleVariant;
size?: ToggleSize;
} = $props();
</script>
<TogglePrimitive.Root
bind:ref
bind:pressed
data-slot="toggle"
class={cn(toggleVariants({ variant, size }), className)}
{...restProps}
/>
With text
Pair an icon with a text label inside the Toggle for labelled formatting controls.
toggle-with-text.svelte
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
export const toggleVariants = tv({
base: "hover:text-foreground aria-pressed:bg-substrate-active focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive gap-1 rounded-(--radius-control) text-sm font-medium transition-colors [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-substrate-hover inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-(length:--ring-width) disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: 'bg-transparent',
outline: 'border-input hover:bg-substrate-hover border bg-transparent'
},
size: {
default:
'h-9 min-w-9 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5',
sm: 'h-8 min-w-8 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
lg: 'h-10 min-w-10 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ToggleVariant = VariantProps<typeof toggleVariants>['variant'];
export type ToggleSize = VariantProps<typeof toggleVariants>['size'];
export type ToggleVariants = VariantProps<typeof toggleVariants>;
</script>
<script lang="ts">
import { Toggle as TogglePrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
pressed = $bindable(false),
class: className,
size = 'default',
variant = 'default',
...restProps
}: TogglePrimitive.RootProps & {
variant?: ToggleVariant;
size?: ToggleSize;
} = $props();
</script>
<TogglePrimitive.Root
bind:ref
bind:pressed
data-slot="toggle"
class={cn(toggleVariants({ variant, size }), className)}
{...restProps}
/>
States
default
outline
toggle.svelte
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
export const toggleVariants = tv({
base: "hover:text-foreground aria-pressed:bg-substrate-active focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive gap-1 rounded-(--radius-control) text-sm font-medium transition-colors [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-substrate-hover inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-(length:--ring-width) disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: 'bg-transparent',
outline: 'border-input hover:bg-substrate-hover border bg-transparent'
},
size: {
default:
'h-9 min-w-9 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5',
sm: 'h-8 min-w-8 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
lg: 'h-10 min-w-10 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ToggleVariant = VariantProps<typeof toggleVariants>['variant'];
export type ToggleSize = VariantProps<typeof toggleVariants>['size'];
export type ToggleVariants = VariantProps<typeof toggleVariants>;
</script>
<script lang="ts">
import { Toggle as TogglePrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
pressed = $bindable(false),
class: className,
size = 'default',
variant = 'default',
...restProps
}: TogglePrimitive.RootProps & {
variant?: ToggleVariant;
size?: ToggleSize;
} = $props();
</script>
<TogglePrimitive.Root
bind:ref
bind:pressed
data-slot="toggle"
class={cn(toggleVariants({ variant, size }), className)}
{...restProps}
/>
API reference
Inherits bits-ui Toggle.Root props via spread. Pressed is two-way bindable; aria-pressed drives the muted fill.
| Prop | Type | Default | Description |
|---|---|---|---|
boolean (bindable) | false | Whether the toggle is currently pressed. Two-way bindable. | |
'default' | 'outline' | 'default' | Presentation. Outline renders a border that stays through all states. | |
'sm' | 'default' | 'lg' | 'default' | Height and horizontal padding. Icons auto-size to match. | |
boolean | false | Disables the toggle and drops opacity to 50%. | |
(pressed: boolean) => void | — | Fires whenever the pressed state changes. | |
HTMLButtonElement | null | null | Two-way-bindable element reference. | |
string | — | Merged onto the root via tailwind-merge. |