Toggle Group

ui-toggle-group joins ui-toggle children into one control with a shared value. With type="single" at most one toggle is pressed, like a segmented control. With type="multiple" any number can be pressed, like the bold, italic and underline buttons of an editor. The value of each ui-toggle identifies it.

The group has the group role and moves focus between its toggles with the arrow keys. It is form-associated, and the pressed values are submitted under its name, separated by commas.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toggle-group.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toggle.html"></import-element>

  <ui-toggle-group value="left" aria-label="Text alignment">
    <ui-toggle value="left" aria-label="Align left">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M3 6h18M3 12h10M3 18h14" /></svg>
    </ui-toggle>
    <ui-toggle value="center" aria-label="Align center">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M3 6h18M7 12h10M5 18h14" /></svg>
    </ui-toggle>
    <ui-toggle value="right" aria-label="Align right">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M3 6h18M11 12h10M7 18h14" /></svg>
    </ui-toggle>
  </ui-toggle-group>
</body>
</html>

Examples

Multiple

With type="multiple", each toggle is pressed independently and value lists every pressed toggle.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toggle-group.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toggle.html"></import-element>

  <ui-toggle-group type="multiple" value="bold,underline" aria-label="Text formatting">
    <ui-toggle value="bold" variant="outline" aria-label="Bold">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M6 12h9a4 4 0 0 1 0 8H6V4h8a4 4 0 0 1 0 8" /></svg>
    </ui-toggle>
    <ui-toggle value="italic" variant="outline" aria-label="Italic">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M19 4h-9M14 20H5M15 4 9 20" /></svg>
    </ui-toggle>
    <ui-toggle value="underline" variant="outline" aria-label="Underline">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M6 4v6a6 6 0 0 0 12 0V4M4 20h16" /></svg>
    </ui-toggle>
  </ui-toggle-group>
</body>
</html>

Outline segmented control

Outline toggles with text make a segmented control. In single mode, clicking the pressed toggle again unpresses it and the value becomes empty.

Previewindex.html
Day Week Month
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toggle-group.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toggle.html"></import-element>

  <ui-toggle-group value="week" aria-label="Range">
    <ui-toggle value="day" variant="outline">Day</ui-toggle>
    <ui-toggle value="week" variant="outline">Week</ui-toggle>
    <ui-toggle value="month" variant="outline">Month</ui-toggle>
  </ui-toggle-group>
</body>
</html>

Listening for changes

The group fires change after every click. Read the new value from the group. The toggles' own change events stop at the group.

Previewindex.htmltoggle-group-change-demo.html
List Grid Board

View:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toggle-group.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toggle.html"></import-element>

  <import-element from="./toggle-group-change-demo.html"></import-element>
  <toggle-group-change-demo></toggle-group-change-demo>
</body>
</html>
<custom-element name="toggle-group-change-demo">
  <script>
    const view = useState({ mode: "grid" });

    function changed(group) {
      view.mode = group.value || "none";
    }
  </script>

  <style>
    :host {
      display: flex;
      align-items: center;
      gap: calc(var(--spacing) * 3);
    }
    p {
      margin: 0;
      font-size: var(--text-sm);
      color: var(--muted-foreground);
    }
  </style>

  <ui-toggle-group value="grid" aria-label="View" onchange="changed(this)">
    <ui-toggle value="list" variant="outline" size="sm">List</ui-toggle>
    <ui-toggle value="grid" variant="outline" size="sm">Grid</ui-toggle>
    <ui-toggle value="board" variant="outline" size="sm">Board</ui-toggle>
  </ui-toggle-group>
  <p>View: <slot name="#view.mode"></slot></p>
</custom-element>

In a form

The group submits value under its name: one value in single mode, a comma-separated list in multiple mode, or an empty string when nothing is pressed. Reset returns to the value attribute.

Previewindex.htmltoggle-group-form-demo.html
S M L Cheese Olives Basil
Order Reset
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle Group demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toggle-group.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toggle.html"></import-element>

  <import-element from="./toggle-group-form-demo.html"></import-element>
  <toggle-group-form-demo></toggle-group-form-demo>
</body>
</html>
<custom-element name="toggle-group-form-demo">
  <script>
    const result = useState({ text: "Submit the form to see its data." });

    function submit(event) {
      event.preventDefault();
      result.text = [...new FormData(event.target)].map(([name, value]) => `${name}=${value}`).join("\n");
    }

    function reset() {
      result.text = "The form was reset.";
    }
  </script>

  <style>
    :host {
      display: grid;
      gap: calc(var(--spacing) * 4);
      width: 100%;
      max-width: calc(var(--spacing) * 80);
    }
    form {
      display: grid;
      gap: calc(var(--spacing) * 3);
      justify-items: start;
    }
    .actions {
      display: flex;
      gap: calc(var(--spacing) * 2);
    }
    output {
      display: block;
      box-sizing: border-box;
      width: 100%;
      padding: calc(var(--spacing) * 3);
      border-radius: var(--radius-md);
      background: var(--muted);
      font-family: var(--font-mono);
      font-size: var(--text-xs);
      white-space: pre-wrap;
    }
  </style>

  <form onsubmit="submit(event)" onreset="reset()">
    <ui-toggle-group name="size" value="m" aria-label="Size">
      <ui-toggle value="s" variant="outline">S</ui-toggle>
      <ui-toggle value="m" variant="outline">M</ui-toggle>
      <ui-toggle value="l" variant="outline">L</ui-toggle>
    </ui-toggle-group>
    <ui-toggle-group name="toppings" type="multiple" aria-label="Toppings">
      <ui-toggle value="cheese" variant="outline">Cheese</ui-toggle>
      <ui-toggle value="olives" variant="outline">Olives</ui-toggle>
      <ui-toggle value="basil" variant="outline">Basil</ui-toggle>
    </ui-toggle-group>
    <div class="actions">
      <ui-button type="submit">Order</ui-button>
      <ui-button type="reset" variant="outline">Reset</ui-button>
    </div>
  </form>
  <output><slot name="#result.text"></slot></output>
</custom-element>

API

Attributes

Attribute Type Default Description
type "single" | "multiple" "single" Whether one or several toggles can be pressed.
value string "" Pressed toggle values, separated by commas. The attribute is the initial and reset value.
orientation "horizontal" | "vertical" "horizontal" Layout direction, and which arrow keys move focus.

Properties

Property Type Description
value string Pressed values as a comma-separated string. Setting it presses the matching toggles.

Events

Event Detail Description
change none Fired after a toggle in the group is clicked and value is updated.

Slots

Slot Description
(default) The ui-toggle elements. Every child except the first gets --ui-radius-start: 0, and every child except the last gets --ui-radius-end: 0.

Child elements

Element Attributes read Description
ui-toggle value, pressed The group sets each toggle's pressed from its own value. A toggle's value must not contain a comma.

CSS parts

Part Description
group The display: contents wrapper that carries the focusgroup attribute.

CSS states

State Description
:state(attr-orientation-is-vertical) Set for the vertical layout.

Forms

ui-toggle-group is form-associated and submits value under its name, including an empty string when nothing is pressed. form.reset() restores the value attribute and the pressed toggles follow it. The group has no disabled attribute. Disable individual toggles instead.

Accessibility

The host has role="group". Name it with aria-label. Each toggle is a button with aria-pressed. The toggles form a native focusgroup. The arrow keys (Left and Right, or Up and Down when vertical) move focus between them, wrapping at the ends, and Home and End jump to the first and last toggle. Space or Enter presses the focused toggle.

Edit this page on GitHub · View as Markdown