Skip to main content

Theme config

Theme mode

Color palette

Grays

Border radii/radiuses/radiopedes/you know
Border radius
Field border radius
Button border radius
All
Components
Guides
API
Recent

Components

Toggle

Buttons (disguised as input checkbox/radio) that can be toggled on and off.

Full support Supported since v125. Full support Supported since v128. Full support Supported since v18.

Toggle button

---
import { ToggleButton } from "@opui/astro"
---
<ToggleButton name="standalone-demo-1">Toggle me</ToggleButton>
<ToggleButton name="standalone-demo-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><path d="M12 21a9 9 0 1 0-9-9 9 9 0 0 0 9 9Z"></path><path d="M12 8v4"
></path><path d="M12 16h.01"></path></svg
>
Toggle with icon
</ToggleButton>
<label class="toggle-button">
<input id="toggle-1" name="standalone-demo-1" type="checkbox" /> Toggle me
</label>
<label class="toggle-button">
<input id="toggle-2" name="standalone-demo-2" type="checkbox" />
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 21a9 9 0 1 0-9-9 9 9 0 0 0 9 9Z"></path>
<path d="M12 8v4"></path>
<path d="M12 16h.01"></path>
</svg>
Toggle with icon
</label>

Toggle group

Group toggle buttons by wrapping them in a ToggleGroup component.

Multi-select

Use selection="multiple" for multi-select groups.

---
import { ToggleGroup, ToggleButton } from "@opui/astro"
---
<ToggleGroup name="text-style">
<ToggleButton value="bold" aria-label="Bold"><strong>B</strong></ToggleButton>
<ToggleButton value="italic" aria-label="Italic"><i>I</i></ToggleButton>
<ToggleButton value="underline" aria-label="Underline"><u>U</u></ToggleButton>
</ToggleGroup>
<div class="toggle-group" role="group">
<label class="toggle-button">
<input
id="toggle-3"
name="text-style"
type="checkbox"
value="bold"
aria-label="Bold"
/>
<strong>B</strong>
</label>
<label class="toggle-button">
<input
id="toggle-4"
name="text-style"
type="checkbox"
value="italic"
aria-label="Italic"
/>
<i>I</i>
</label>
<label class="toggle-button">
<input
id="toggle-5"
name="text-style"
type="checkbox"
value="underline"
aria-label="Underline"
/>
<u>U</u>
</label>
</div>

Single-select

Use selection="single" for single-select groups.

---
import { ToggleGroup, ToggleButton } from "@opui/astro"
---
<ToggleGroup selection="single" name="alignment">
<ToggleButton value="left" aria-label="Align left">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M3 21h18v-2H3v2zm0-4h12v-2H3v2zm0-4h18v-2H3v2zm0-4h12v-2H3v2zm0-6v2h18V3H3z"
></path></svg
>
</ToggleButton>
<ToggleButton value="center" checked aria-label="Align center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M3 21h18v-2H3v2zm4-4h10v-2H7v2zm-4-4h18v-2H3v2zm4-4h10v-2H7v2zM3 3v2h18V3H3z"
></path></svg
>
</ToggleButton>
<ToggleButton value="right" aria-label="Align right">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12v-2H9v2zM3 3v2h18V3H3z"
></path></svg
>
</ToggleButton>
</ToggleGroup>
<div class="toggle-group" role="radiogroup">
<label class="toggle-button">
<input
id="toggle-6"
name="alignment"
type="radio"
value="left"
aria-label="Align left"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M3 21h18v-2H3v2zm0-4h12v-2H3v2zm0-4h18v-2H3v2zm0-4h12v-2H3v2zm0-6v2h18V3H3z"
></path>
</svg>
</label>
<label class="toggle-button">
<input
id="toggle-7"
name="alignment"
type="radio"
value="center"
checked
aria-label="Align center"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M3 21h18v-2H3v2zm4-4h10v-2H7v2zm-4-4h18v-2H3v2zm4-4h10v-2H7v2zM3 3v2h18V3H3z"
></path>
</svg>
</label>
<label class="toggle-button">
<input
id="toggle-8"
name="alignment"
type="radio"
value="right"
aria-label="Align right"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12v-2H9v2zM3 3v2h18V3H3z"
></path>
</svg>
</label>
</div>

Text + icon

---
import { ToggleGroup, ToggleButton } from "@opui/astro"
---
<ToggleGroup selection="single" name="transport">
<ToggleButton value="walking" checked>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M13 6.5A2.25 2.25 0 1 0 13 2a2.25 2.25 0 0 0 0 4.5m-2.639-.081c.185.045.35.146.493.272a3.24 3.24 0 0 0 2.904.72c.186-.044.379-.056.564-.01l.132.033a1.5 1.5 0 0 1 .919.673l1.332 2.177a1 1 0 0 0 .657.46l1.431.285a1.5 1.5 0 0 1-.587 2.942l-2.504-.5a1.5 1.5 0 0 1-.986-.688l-.183-.3a.54.54 0 0 0-.966.09a1.5 1.5 0 0 0 .17 1.389l.994 1.433a1.5 1.5 0 0 1 .265.767l.25 4.25a1.5 1.5 0 0 1-2.995.176l-.2-3.391a1 1 0 0 0-.247-.602l-.851-.968a.88.88 0 0 0-1.477.252L7.39 21.061a1.5 1.5 0 0 1-2.783-1.122l3.076-7.634q.02-.081.052-.162l.565-1.47a.469.469 0 0 0-.865-.362l-1.268 2.806a1.5 1.5 0 0 1-2.735-1.232l1.624-3.61a1.5 1.5 0 0 1 .846-.792l3.075-1.14a1.5 1.5 0 0 1 .883-.049z"
></path>
</svg>
Walking
</ToggleButton>
<ToggleButton value="cycling">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12.75 3a.75.75 0 0 0 0 1.5h1.427l.955 3.5H8.5V5.75A.75.75 0 0 0 7.75 5h-3a.75.75 0 0 0 0 1.5H7v2.188L6.698 10.5a4.25 4.25 0 1 0 4.298 4.065l4.656-4.657l.274 1.003a4.25 4.25 0 1 0 1.447-.394l-1.9-6.964A.75.75 0 0 0 14.75 3zm3.58 9.394l.696 2.553a.75.75 0 1 0 1.448-.394L17.777 12a2.75 2.75 0 1 1-1.447.394m-5.765.48a4.26 4.26 0 0 0-2.387-2.128L8.385 9.5h5.554zm-2.64-.611c.71.336 1.254.968 1.471 1.737h-1.76zm-1.48-.246l-.435 2.61a.75.75 0 0 0 .74.873h2.646a2.751 2.751 0 1 1-2.95-3.483"
></path>
</svg>
Cycling
</ToggleButton>
<ToggleButton value="commuting">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M16.25 3A3.75 3.75 0 0 1 20 6.75v9a3.75 3.75 0 0 1-2.89 3.651l2.462 1.172a.75.75 0 0 1-.55 1.392l-.095-.038L13.83 19.5h-3.661l-5.097 2.427a.75.75 0 1 1-.645-1.354L6.89 19.4A3.75 3.75 0 0 1 4 15.75v-9A3.75 3.75 0 0 1 7.75 3zM8 15a1 1 0 1 0 0 2a1 1 0 0 0 0-2m8 0a1 1 0 1 0 0 2a1 1 0 0 0 0-2m.25-10.5h-8.5A2.25 2.25 0 0 0 5.5 6.75v5.75h13V6.75a2.25 2.25 0 0 0-2.25-2.25m-3 1.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1 0-1.5z"
></path>
</svg>
Commuting
</ToggleButton>
</ToggleGroup>
<div class="toggle-group" role="radiogroup">
<label class="toggle-button">
<input
id="toggle-9"
name="transport"
type="radio"
value="walking"
checked
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M13 6.5A2.25 2.25 0 1 0 13 2a2.25 2.25 0 0 0 0 4.5m-2.639-.081c.185.045.35.146.493.272a3.24 3.24 0 0 0 2.904.72c.186-.044.379-.056.564-.01l.132.033a1.5 1.5 0 0 1 .919.673l1.332 2.177a1 1 0 0 0 .657.46l1.431.285a1.5 1.5 0 0 1-.587 2.942l-2.504-.5a1.5 1.5 0 0 1-.986-.688l-.183-.3a.54.54 0 0 0-.966.09a1.5 1.5 0 0 0 .17 1.389l.994 1.433a1.5 1.5 0 0 1 .265.767l.25 4.25a1.5 1.5 0 0 1-2.995.176l-.2-3.391a1 1 0 0 0-.247-.602l-.851-.968a.88.88 0 0 0-1.477.252L7.39 21.061a1.5 1.5 0 0 1-2.783-1.122l3.076-7.634q.02-.081.052-.162l.565-1.47a.469.469 0 0 0-.865-.362l-1.268 2.806a1.5 1.5 0 0 1-2.735-1.232l1.624-3.61a1.5 1.5 0 0 1 .846-.792l3.075-1.14a1.5 1.5 0 0 1 .883-.049z"
></path>
</svg>
Walking
</label>
<label class="toggle-button">
<input id="toggle-10" name="transport" type="radio" value="cycling" />
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12.75 3a.75.75 0 0 0 0 1.5h1.427l.955 3.5H8.5V5.75A.75.75 0 0 0 7.75 5h-3a.75.75 0 0 0 0 1.5H7v2.188L6.698 10.5a4.25 4.25 0 1 0 4.298 4.065l4.656-4.657l.274 1.003a4.25 4.25 0 1 0 1.447-.394l-1.9-6.964A.75.75 0 0 0 14.75 3zm3.58 9.394l.696 2.553a.75.75 0 1 0 1.448-.394L17.777 12a2.75 2.75 0 1 1-1.447.394m-5.765.48a4.26 4.26 0 0 0-2.387-2.128L8.385 9.5h5.554zm-2.64-.611c.71.336 1.254.968 1.471 1.737h-1.76zm-1.48-.246l-.435 2.61a.75.75 0 0 0 .74.873h2.646a2.751 2.751 0 1 1-2.95-3.483"
></path>
</svg>
Cycling
</label>
<label class="toggle-button">
<input id="toggle-11" name="transport" type="radio" value="commuting" />
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M16.25 3A3.75 3.75 0 0 1 20 6.75v9a3.75 3.75 0 0 1-2.89 3.651l2.462 1.172a.75.75 0 0 1-.55 1.392l-.095-.038L13.83 19.5h-3.661l-5.097 2.427a.75.75 0 1 1-.645-1.354L6.89 19.4A3.75 3.75 0 0 1 4 15.75v-9A3.75 3.75 0 0 1 7.75 3zM8 15a1 1 0 1 0 0 2a1 1 0 0 0 0-2m8 0a1 1 0 1 0 0 2a1 1 0 0 0 0-2m.25-10.5h-8.5A2.25 2.25 0 0 0 5.5 6.75v5.75h13V6.75a2.25 2.25 0 0 0-2.25-2.25m-3 1.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1 0-1.5z"
></path>
</svg>
Commuting
</label>
</div>

Vertical orientation

Change the layout of the group with the orientation="vertical" prop.

---
import { ToggleGroup, ToggleButton } from "@opui/astro"
---
<ToggleGroup
selection="single"
name="alignment-vertical"
orientation="vertical"
>
<ToggleButton value="left" aria-label="Align left">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M3 21h18v-2H3v2zm0-4h12v-2H3v2zm0-4h18v-2H3v2zm0-4h12v-2H3v2zm0-6v2h18V3H3z"
></path></svg
>
</ToggleButton>
<ToggleButton value="center" checked aria-label="Align center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M3 21h18v-2H3v2zm4-4h10v-2H7v2zm-4-4h18v-2H3v2zm4-4h10v-2H7v2zM3 3v2h18V3H3z"
></path></svg
>
</ToggleButton>
<ToggleButton value="right" aria-label="Align right">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12v-2H9v2zM3 3v2h18V3H3z"
></path></svg
>
</ToggleButton>
</ToggleGroup>
<div class="toggle-group vertical" role="radiogroup">
<label class="toggle-button">
<input
id="toggle-12"
name="alignment-vertical"
type="radio"
value="left"
aria-label="Align left"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M3 21h18v-2H3v2zm0-4h12v-2H3v2zm0-4h18v-2H3v2zm0-4h12v-2H3v2zm0-6v2h18V3H3z"
></path>
</svg>
</label>
<label class="toggle-button">
<input
id="toggle-13"
name="alignment-vertical"
type="radio"
value="center"
checked
aria-label="Align center"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M3 21h18v-2H3v2zm4-4h10v-2H7v2zm-4-4h18v-2H3v2zm4-4h10v-2H7v2zM3 3v2h18V3H3z"
></path>
</svg>
</label>
<label class="toggle-button">
<input
id="toggle-14"
name="alignment-vertical"
type="radio"
value="right"
aria-label="Align right"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12v-2H9v2zM3 3v2h18V3H3z"
></path>
</svg>
</label>
</div>

Sizes

Choose between three sizes: default, .x-small and .small.

---
import { ToggleButton } from "@opui/astro"
---
<ToggleButton size="x-small"> x-small </ToggleButton>
<ToggleButton size="small"> small </ToggleButton>
<ToggleButton> default </ToggleButton>
<label class="toggle-button x-small">
<input id="toggle-15" name="alignment-vertical" type="radio" /> x-small
</label>
<label class="toggle-button small">
<input id="toggle-16" name="alignment-vertical" type="radio" /> small
</label>
<label class="toggle-button">
<input id="toggle-17" name="alignment-vertical" type="radio" /> default
</label>

Anatomy

API

Toggle Button

Individual toggle button component.

Prop Type Default Description
disabled boolean false Whether the toggle button is disabled.
label string - The label text for the toggle button.
name string - The name of the input element. Inherited from ToggleGroup if used within one.
pressed boolean false Whether the toggle button is pressed. Maps to aria-pressed in checkbox mode and to checked on the underlying input.
size "default" | "small" | "x-small" "default" The size of the toggle button.
type "checkbox" | "radio" "checkbox" The type of input element. Inherited from ToggleGroup (driven by the selection prop) if used within one.
value string - The value attribute of the input element. Defaults to label if not provided.

Toggle Group

Container for grouping multiple toggle buttons.

Prop Type Default Description
name string - The name property for child inputs. Used for native form submission.
size "default" | "small" | "x-small" "default" The size of the toggle group.
orientation "vertical" - Changes the layout direction of the group.
selection "single" | "multiple" "multiple" The selection mode. "single" maps to radio inputs; "multiple" maps to checkbox inputs.

Browser support

Full support Supported since v125. Full support Supported since v128. Full support Supported since v18.

See also the full browser support guide.

Installation

@layer components.root {
:where(.toggle-group) {
--_bg-color: var(--surface-default);
--_border-color: var(--border-color);
--_border-radius: var(--button-border-radius);
background-color: var(--_bg-color);
border: 1px solid var(--_border-color);
border-radius: var(--_border-radius);
display: inline-flex;
inline-size: fit-content;
overflow: hidden;
/* Sizes */
&.small {
.toggle-button {
--_size: var(--field-size-small);
}
}
&.x-small {
.toggle-button {
--_size: var(--field-size-x-small);
}
}
/* Toggle button */
.toggle-button {
border: 0;
border-radius: 0;
margin: 0;
}
&:not(.vertical) {
.toggle-button {
border-inline-end: 1px solid var(--_border-color);
&:last-of-type {
border-inline-end: 0;
}
}
}
&.vertical {
flex-direction: column;
.toggle-button {
border-block-end: 1px solid var(--_border-color);
&:last-of-type {
border-block-end: 0;
}
}
}
}
}
/* TODO:
* Somehow handle when a group gets too wide for its container. Should it be handled?
*/
@layer components.root {
:where(.toggle-button) {
--_button-bg-color: transparent;
--_size: var(--field-size);
--_svg-size: var(--size-4);
align-items: center;
background-color: var(--_button-bg-color);
block-size: var(--_size);
border: 1px solid var(--border-color);
border-radius: var(--button-border-radius);
color: var(--text-primary);
cursor: pointer;
display: inline-flex;
flex: auto;
gap: var(--size-2);
inline-size: fit-content;
justify-content: center;
min-inline-size: var(--_size);
outline-offset: calc(-1 * var(--size-2));
padding: 0 var(--size-2);
position: relative;
user-select: none;
input[type="checkbox"],
input[type="radio"] {
/* utils.css > .sr-only */
block-size: 1px;
clip-path: inset(50%);
inline-size: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
}
/* Element states */
@media (pointer: fine) {
&:hover:not(.disabled) {
--_button-bg-color: light-dark(oklch(0% 0 0 / 0.04), oklch(100% 0 0 / 0.08));
}
}
&:has(input:focus-visible) {
--focus-ring-color: var(--text-muted);
--focus-ring-offset: -6px;
outline: var(--focus-ring-width) var(--focus-ring-style) var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
/* Disabled */
&.disabled {
border-color: color-mix(in oklch, var(--border-color) 50%, transparent);
color: color-mix(in oklch, var(--text-primary) 30%, transparent);
cursor: not-allowed;
}
/* svgs */
svg {
block-size: var(--_svg-size);
flex-shrink: 0;
inline-size: var(--_svg-size);
}
/* Selected */
&:has(:where(input[type="checkbox"], input[type="radio"]):checked) {
--_button-bg-color: oklch(from var(--primary) l c h / 25%);
@media (pointer: fine) {
&:hover {
--_button-bg-color: oklch(from var(--primary) l c h / 35%);
}
}
}
/* Sizes */
&.small {
--_size: var(--field-size-small);
}
&.x-small {
--_size: var(--field-size-x-small);
}
}
}