<script lang="ts">
import * as ToggleGroup from "$lib/components/ui/toggle-group/index.js";
import BookmarkIcon from "@lucide/svelte/icons/bookmark";
import HeartIcon from "@lucide/svelte/icons/heart";
import StarIcon from "@lucide/svelte/icons/star";
</script>
<ToggleGroup.Root type="multiple" variant="outline" spacing={2} size="sm">
<ToggleGroup.Item
value="star"
aria-label="Toggle star"
class="data-[state=on]:bg-transparent data-[state=on]:*:[svg]:fill-yellow-500 data-[state=on]:*:[svg]:stroke-yellow-500"
>
<StarIcon />
Star
</ToggleGroup.Item>
<ToggleGroup.Item
value="heart"
aria-label="Toggle heart"
class="data-[state=on]:bg-transparent data-[state=on]:*:[svg]:fill-red-500 data-[state=on]:*:[svg]:stroke-red-500"
>
<HeartIcon />
Heart
</ToggleGroup.Item>
<ToggleGroup.Item
value="bookmark"
aria-label="Toggle bookmark"
class="data-[state=on]:bg-transparent data-[state=on]:*:[svg]:fill-blue-500 data-[state=on]:*:[svg]:stroke-blue-500"
>
<BookmarkIcon />
Bookmark
</ToggleGroup.Item>
</ToggleGroup.Root> Installation
pnpm dlx shadcn-svelte@latest add toggle-group Install bits-ui:
pnpm add bits-ui -D Copy and paste the following code into your project.
import Root from './toggle-group.svelte';
import Item from './toggle-group-item.svelte';
export {
Root,
Item,
//
Root as ToggleGroup,
Item as ToggleGroupItem
};
<script lang="ts">
import { ToggleGroup as ToggleGroupPrimitive } from 'bits-ui';
import { getToggleGroupCtx } from './toggle-group.svelte';
import { cn } from '$UTILS$.js';
import { type ToggleVariants, toggleVariants } from '$UI$/toggle/index.js';
let {
ref = $bindable(null),
value = $bindable(),
class: className,
size,
variant,
...restProps
}: ToggleGroupPrimitive.ItemProps & ToggleVariants = $props();
const ctx = getToggleGroupCtx();
</script>
<ToggleGroupPrimitive.Item
bind:ref
data-slot="toggle-group-item"
data-variant={ctx.variant || variant}
data-size={ctx.size || size}
data-spacing={ctx.spacing}
class={cn(
'data-[state=on]:border-[#b9d765]/60 data-[state=on]:bg-[#b9d765]/15 data-[state=on]:text-zinc-50 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-6 group-data-[spacing=0]/toggle-group:shadow-none group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-4 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-4 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-full group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-full group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-full group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-full shrink-0 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t',
toggleVariants({
variant: ctx.variant || variant,
size: ctx.size || size
}),
className
)}
{value}
{...restProps}
/>
<script lang="ts" module>
import { getContext, setContext } from 'svelte';
import type { VariantProps } from 'tailwind-variants';
import { toggleVariants } from '$UI$/toggle/index.js';
type ToggleVariants = VariantProps<typeof toggleVariants>;
interface ToggleGroupContext extends ToggleVariants {
spacing?: number;
orientation?: 'horizontal' | 'vertical';
}
export function setToggleGroupCtx(props: ToggleGroupContext) {
setContext('toggleGroup', props);
}
export function getToggleGroupCtx() {
return getContext<Required<ToggleGroupContext>>('toggleGroup');
}
</script>
<script lang="ts">
import { ToggleGroup as ToggleGroupPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
value = $bindable(),
class: className,
size = 'default',
spacing = 0,
orientation = 'horizontal',
variant = 'default',
...restProps
}: ToggleGroupPrimitive.RootProps &
ToggleVariants & {
spacing?: number;
orientation?: 'horizontal' | 'vertical';
} = $props();
setToggleGroupCtx({
get variant() {
return variant;
},
get size() {
return size;
},
get spacing() {
return spacing;
},
get orientation() {
return orientation;
}
});
</script>
<!--
Discriminated Unions + Destructing (required for bindable) do not
get along, so we shut typescript up by casting `value` to `never`.
-->
<ToggleGroupPrimitive.Root
bind:value={value as never}
bind:ref
{orientation}
data-slot="toggle-group"
data-variant={variant}
data-size={size}
data-spacing={spacing}
style={`--gap: ${spacing}`}
class={cn(
'data-[spacing=0]:data-[variant=outline]:rounded-full group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] data-vertical:flex-col data-vertical:items-stretch',
className
)}
{...restProps}
/>
Usage
<script lang="ts">
import * as ToggleGroup from '$lib/components/ui/toggle-group/index.js';
</script> <ToggleGroup.Root type="single">
<ToggleGroup.Item value="a">A</ToggleGroup.Item>
<ToggleGroup.Item value="b">B</ToggleGroup.Item>
<ToggleGroup.Item value="c">C</ToggleGroup.Item>
</ToggleGroup.Root> Examples
Outline
<script lang="ts">
import BoldIcon from "@lucide/svelte/icons/bold";
import ItalicIcon from "@lucide/svelte/icons/italic";
import UnderlineIcon from "@lucide/svelte/icons/underline";
import * as ToggleGroup from "$lib/components/ui/toggle-group/index.js";
</script>
<ToggleGroup.Root variant="outline" type="multiple">
<ToggleGroup.Item value="bold" aria-label="Toggle bold">
<BoldIcon class="size-4" />
</ToggleGroup.Item>
<ToggleGroup.Item value="italic" aria-label="Toggle italic">
<ItalicIcon class="size-4" />
</ToggleGroup.Item>
<ToggleGroup.Item value="strikethrough" aria-label="Toggle strikethrough">
<UnderlineIcon class="size-4" />
</ToggleGroup.Item>
</ToggleGroup.Root> Single
<script lang="ts">
import BoldIcon from "@lucide/svelte/icons/bold";
import ItalicIcon from "@lucide/svelte/icons/italic";
import UnderlineIcon from "@lucide/svelte/icons/underline";
import * as ToggleGroup from "$lib/components/ui/toggle-group/index.js";
</script>
<ToggleGroup.Root type="single">
<ToggleGroup.Item value="bold" aria-label="Toggle bold">
<BoldIcon class="size-4" />
</ToggleGroup.Item>
<ToggleGroup.Item value="italic" aria-label="Toggle italic">
<ItalicIcon class="size-4" />
</ToggleGroup.Item>
<ToggleGroup.Item value="strikethrough" aria-label="Toggle strikethrough">
<UnderlineIcon class="size-4" />
</ToggleGroup.Item>
</ToggleGroup.Root> Small
<script lang="ts">
import BoldIcon from "@lucide/svelte/icons/bold";
import ItalicIcon from "@lucide/svelte/icons/italic";
import UnderlineIcon from "@lucide/svelte/icons/underline";
import * as ToggleGroup from "$lib/components/ui/toggle-group/index.js";
</script>
<ToggleGroup.Root size="sm" type="single">
<ToggleGroup.Item value="bold" aria-label="Toggle bold">
<BoldIcon class="size-4" />
</ToggleGroup.Item>
<ToggleGroup.Item value="italic" aria-label="Toggle italic">
<ItalicIcon class="size-4" />
</ToggleGroup.Item>
<ToggleGroup.Item value="strikethrough" aria-label="Toggle strikethrough">
<UnderlineIcon class="size-4" />
</ToggleGroup.Item>
</ToggleGroup.Root> Large
<script lang="ts">
import BoldIcon from "@lucide/svelte/icons/bold";
import ItalicIcon from "@lucide/svelte/icons/italic";
import UnderlineIcon from "@lucide/svelte/icons/underline";
import * as ToggleGroup from "$lib/components/ui/toggle-group/index.js";
</script>
<ToggleGroup.Root size="lg" type="multiple">
<ToggleGroup.Item value="bold" aria-label="Toggle bold">
<BoldIcon class="size-4" />
</ToggleGroup.Item>
<ToggleGroup.Item value="italic" aria-label="Toggle italic">
<ItalicIcon class="size-4" />
</ToggleGroup.Item>
<ToggleGroup.Item value="strikethrough" aria-label="Toggle strikethrough">
<UnderlineIcon class="size-4" />
</ToggleGroup.Item>
</ToggleGroup.Root> Disabled
<script lang="ts">
import BoldIcon from "@lucide/svelte/icons/bold";
import ItalicIcon from "@lucide/svelte/icons/italic";
import UnderlineIcon from "@lucide/svelte/icons/underline";
import * as ToggleGroup from "$lib/components/ui/toggle-group/index.js";
</script>
<ToggleGroup.Root disabled type="single">
<ToggleGroup.Item value="bold" aria-label="Toggle bold">
<BoldIcon class="size-4" />
</ToggleGroup.Item>
<ToggleGroup.Item value="italic" aria-label="Toggle italic">
<ItalicIcon class="size-4" />
</ToggleGroup.Item>
<ToggleGroup.Item value="strikethrough" aria-label="Toggle strikethrough">
<UnderlineIcon class="size-4" />
</ToggleGroup.Item>
</ToggleGroup.Root> Spacing
Use spacing={2} to add spacing between toggle group items.
<script lang="ts">
import * as ToggleGroup from "$lib/components/ui/toggle-group/index.js";
import BookmarkIcon from "@lucide/svelte/icons/bookmark";
import HeartIcon from "@lucide/svelte/icons/heart";
import StarIcon from "@lucide/svelte/icons/star";
</script>
<ToggleGroup.Root type="multiple" variant="outline" spacing={2} size="sm">
<ToggleGroup.Item
value="star"
aria-label="Toggle star"
class="data-[state=on]:bg-transparent data-[state=on]:*:[svg]:fill-yellow-500 data-[state=on]:*:[svg]:stroke-yellow-500"
>
<StarIcon />
Star
</ToggleGroup.Item>
<ToggleGroup.Item
value="heart"
aria-label="Toggle heart"
class="data-[state=on]:bg-transparent data-[state=on]:*:[svg]:fill-red-500 data-[state=on]:*:[svg]:stroke-red-500"
>
<HeartIcon />
Heart
</ToggleGroup.Item>
<ToggleGroup.Item
value="bookmark"
aria-label="Toggle bookmark"
class="data-[state=on]:bg-transparent data-[state=on]:*:[svg]:fill-blue-500 data-[state=on]:*:[svg]:stroke-blue-500"
>
<BookmarkIcon />
Bookmark
</ToggleGroup.Item>
</ToggleGroup.Root>