Toggle
A button that stays pressed.
import { Div } from "@implementjs/core";
import { BoldIcon, ItalicIcon, UnderlineIcon } from "@implementjs/lucide";
import { Toggle } from "@/lib/components/ui/toggle";
export default function ToggleDemo() {
return Div(
{ class: "flex items-center gap-1" },
Toggle({ pressed: true, "aria-label": "Toggle bold" }, BoldIcon({ "aria-hidden": true })),
Toggle({ "aria-label": "Toggle italic" }, ItalicIcon({ "aria-hidden": true })),
Toggle(
{ "aria-label": "Toggle underline", disabled: true },
UnderlineIcon({ "aria-hidden": true }),
),
);
}Installation
npx jsrepo add @implementjs/ui/toggle
It installs tailwind-variants at the same time.
Copy the file below to src/lib/components/ui/toggle.ts. It imports cn, so copy utils.ts to src/lib/utils.ts too. Then, on top of @implementjs/core and @implementjs/primitives:
npm install tailwind-variants
import { type Child, type ComponentProps } from "@implementjs/core";
import { Toggle as TogglePrimitive } from "@implementjs/primitives";
import { tv, type VariantProps } from "tailwind-variants";
import { createComponent } from "@implementjs/primitives";
import { cn } from "@/lib/utils";
export const toggleVariants = tv({
base: "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-9 min-w-9 px-2",
sm: "h-8 min-w-8 px-1.5",
lg: "h-10 min-w-10 px-2.5",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
export type ToggleVariant = VariantProps<typeof toggleVariants>["variant"];
export type ToggleSize = VariantProps<typeof toggleVariants>["size"];
export type ToggleProps = ComponentProps<typeof TogglePrimitive> & {
variant?: ToggleVariant;
size?: ToggleSize;
};
export const Toggle = createComponent(function Toggle(
{ class: className, variant = "default", size = "default", ...props }: ToggleProps,
...children: Child[]
) {
return TogglePrimitive(
{
...props,
"data-slot": "toggle",
"data-variant": variant,
"data-size": size,
class: cn(toggleVariants({ variant, size }), className),
},
...children,
);
});Usage
A two-state button: bold on or off, a filter applied or not. variant picks transparent or outlined, size the scale — both are also written out as data-variant and data-size for styling from outside.
For a set of them that share state, use a toggle group.
import { signal } from "@implementjs/core";
import { Toggle } from "@/lib/components/ui/toggle";
const bold = signal(false);
Toggle({ pressed: bold, "aria-label": "Toggle bold" }, BoldIcon({ "aria-hidden": true }));
Labelling an icon toggle
A toggle with only an icon in it has no accessible name, so give it one — and hide the icon from the accessibility tree, since it is not the label.
API Reference
Every prop the styling does not consume is forwarded to the Toggle primitive, so the tables below are the whole surface — the behavior props and the styling ones together.
Toggle
A two-state button that can be on or off. Sets aria-pressed. Give it a look and children; it handles the state. Renders a Button; extra props are forwarded onto it.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "outline" | "default" | Transparent by default; outline adds a border. Also set as data-variant. |
size | "default" | "sm" | "lg" | "default" | The toggle's height and padding scale. Also set as data-size. |
pressed | Signal<boolean> | boolean | false | The pressed state. Pass a signal to control it from outside; a boolean seeds uncontrolled state. |
disabled | Signal<boolean> | boolean | false | Prevents toggling. Sets the native disabled attribute and data-disabled. |
| Data attribute | Value |
|---|---|
[data-toggle-root] | Present |
[data-state] | "on" | "off" |
[data-disabled] | Present when disabled |