Toggle
Turn an option on or off with a button that stays pressed.
import { Toggle } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use a toggle for a view or formatting option that applies at once and is read from the button itself: pressed means on. For a setting in a form or a settings list, use a Switch. For a set of related options, use a Toggle Group, and for a one-off action, a Button.
import { useState } from 'react'
import { Toggle } from '@youj/react'
import { CornerDownLeftIcon } from '@youj/icons'
export function WrapLines() {
const [wrap, setWrap] = useState(false)
return (
<Toggle variant="outline" pressed={wrap} onPressedChange={setWrap}>
<CornerDownLeftIcon />
Wrap
</Toggle>
)
}Use pressed and onPressedChange to control it, or defaultPressed to let it hold its own state. Pressed reads as a filled neutral key with full-strength text. The accent stays reserved for actions.
Description
Build log options: Timestamps and Wrap toggles with labels, and an icon-only Pin toggle.
Four icon toggles whose icons morph between their two states as you press.
A Youj state icon inside a toggle can follow the pressed state through its active prop. It morphs to its second state as the toggle presses, and a quick second press reverses it from wherever it is.
<Toggle aria-label="Pin project" pressed={pinned} onPressedChange={setPinned}>
<PinIcon active={pinned} />
</Toggle>The default and outline variants, a labeled toggle, the three sizes and a disabled toggle.
default is borderless, for toolbars that already have a frame. outline adds a border and suits toggles that stand alone. Three sizes, sm, default and lg, match the button heights (28, 32 and 40px).
Code
toggle-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propssound, variant, size (3)
Props
| Prop | Type | |
|---|---|---|
sound | booleanWith sound on ( default true | true |
variant | 'default' | 'outline'default 'default' | 'default' |
size | 'default' | 'sm' | 'lg'default 'default' | 'default' |
Other props pass through: extends TogglePrimitive.Props, VariantProps<typeof toggleVariants>. | ||
The state comes from Base UI's Toggle, and these are the props you'll reach for:
| Prop | Type | Description |
|---|---|---|
pressed | boolean | Controlled state. |
defaultPressed | boolean | Initial state when uncontrolled. Default false. |
onPressedChange | (pressed: boolean, details) => void | Called when the user toggles it. |
disabled | boolean | Ignores presses. |
value | string | Identifies it inside a Toggle Group. |
MotionPress, release, hover and pressed fill · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Pressscales to 0.97 while held | duration.instantease.standard80ms · cubic-bezier(0.2, 0, 0, 1) | 80mscubic-bezier(0.2, 0, 0, 1) | |
| Release, hover and pressed fillscale back, background and text colors | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) |
Reduced motion
No press scale. The fill and text colors still change on the fast token, since nothing moves.
Reduced motion
The press doesn't scale. Colors still change, so the pressed state reads the same, and a state icon switches to its second state instantly instead of morphing.
AccessibilityKeyboard, ARIA, announcements
Accessibility
- It's a
<button>witharia-pressed.SpaceandEntertoggle it. - Keep the name constant and let
aria-pressedcarry the state: a toggle named "Pin project" is announced as pressed or not pressed. Don't swap the label between "Pin" and "Unpin". - Icon-only toggles need an
aria-label. A Youj icon inside is decorative unless you label it. - For a group of related toggles, use a Toggle Group, which gives them one tab stop and arrow-key navigation.