Actions
Toggle
A two-state pressable button built on react-aria-components, styled with the quebi design system. The selected state lights up with brand teal.
actiontoggleinteractivebutton
Intents
Outline is bordered; plain is borderless. Both light up teal when selected.
Sizes
Icon only
Square sizes for toolbar-style icon toggles.
Disabled
Controlled
Source
Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
"use client"
import {
composeRenderProps,
ToggleButton as TogglePrimitive,
type ToggleButtonProps,
} from "react-aria-components"
import { tv, type VariantProps } from "tailwind-variants"
import { cn } from "@/lib/utils"
/**
* Toggle — quebi design system
*
* A two-state pressable button (think bold/italic in a toolbar). The selected
* state lights up with brand teal; depth comes from a quebi glow, never a drop
* shadow.
*
* Intents: outline (bordered) / plain (borderless). Sizes follow the button
* scale, including square (sq-*) icon-only variants.
*/
export const toggleStyles = tv({
base: [
"inline-flex items-center justify-center gap-2",
"font-sans font-semibold whitespace-nowrap select-none cursor-pointer",
"rounded-quebi-sm border border-solid",
"transition-all duration-200 ease-out",
"outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-quebi-brand/50 focus-visible:ring-offset-2 focus-visible:ring-offset-quebi-bg",
"disabled:opacity-50 disabled:cursor-not-allowed",
// react-aria slot convention — icons inherit current color
"*:data-[slot=icon]:shrink-0 *:data-[slot=icon]:self-center",
],
variants: {
intent: {
outline: [
"bg-transparent border-cyan-500/20 text-quebi-fg-muted",
"hover:text-white hover:border-quebi-brand",
"selected:bg-quebi-brand selected:border-quebi-brand selected:text-quebi-bg selected:shadow-quebi-glow selected:hover:bg-quebi-brand-hover selected:hover:border-quebi-brand-hover selected:hover:text-quebi-bg",
],
plain: [
"bg-transparent border-transparent text-quebi-fg-muted",
"hover:bg-white/[0.04] hover:text-white",
"selected:bg-quebi-brand selected:border-quebi-brand selected:text-quebi-bg selected:shadow-quebi-glow selected:hover:bg-quebi-brand-hover selected:hover:text-quebi-bg",
],
},
size: {
xs: ["text-xs px-2.5 py-1.5", "*:data-[slot=icon]:size-3.5"],
sm: ["text-sm px-3 py-2", "*:data-[slot=icon]:size-4"],
md: ["text-base px-5 py-2.5", "*:data-[slot=icon]:size-5"],
lg: ["text-lg px-6 py-3", "*:data-[slot=icon]:size-5"],
// Square / icon-only
"sq-xs": "size-7 p-0 *:data-[slot=icon]:size-3.5",
"sq-sm": "size-9 p-0 *:data-[slot=icon]:size-4",
"sq-md": "size-11 p-0 *:data-[slot=icon]:size-5",
"sq-lg": "size-12 p-0 *:data-[slot=icon]:size-6",
},
isCircle: {
true: "rounded-full",
false: "",
},
},
defaultVariants: {
intent: "plain",
size: "md",
isCircle: false,
},
})
export interface ToggleProps extends ToggleButtonProps, VariantProps<typeof toggleStyles> {
ref?: React.Ref<HTMLButtonElement>
}
export function Toggle({ className, intent, size, isCircle, ref, ...props }: ToggleProps) {
return (
<TogglePrimitive
ref={ref}
{...props}
className={composeRenderProps(className, (className) =>
cn(toggleStyles({ intent, size, isCircle }), className),
)}
/>
)
}