Toggle

ui-toggle is a native <button> with aria-pressed. Each click flips its pressed state and fires change. Use it for an on/off option that is shown as a button, such as bold in a text toolbar. For several toggles that share one value, wrap them in a Toggle Group.

A toggle is not form-associated on its own and submits nothing. Inside a ui-toggle-group with a name, the group submits the pressed values.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle 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.html"></import-element>

  <ui-toggle 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>
</body>
</html>

Examples

Outline

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle 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.html"></import-element>

  <ui-toggle 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>
</body>
</html>

With text

Previewindex.html
Pin
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle 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.html"></import-element>

  <ui-toggle variant="outline">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 17v5M9 10.76V6h6v4.76l2 2.24v2H7v-2z" /><path d="M8 2h8" /></svg>
    Pin
  </ui-toggle>
</body>
</html>

Sizes

Previewindex.html
Small Default Large
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle 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.html"></import-element>

  <ui-toggle size="sm" variant="outline">Small</ui-toggle>
  <ui-toggle variant="outline">Default</ui-toggle>
  <ui-toggle size="lg" variant="outline">Large</ui-toggle>
</body>
</html>

Pressed and disabled

pressed sets the initial state and reflects to the attribute as the user toggles.

Previewindex.html
Pressed Disabled
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle 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.html"></import-element>

  <ui-toggle pressed variant="outline">Pressed</ui-toggle>
  <ui-toggle disabled variant="outline">Disabled</ui-toggle>
</body>
</html>

Listening for changes

Previewindex.htmltoggle-change-demo.html
Wrap lines

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle 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.html"></import-element>

  <import-element from="./toggle-change-demo.html"></import-element>
  <toggle-change-demo></toggle-change-demo>
</body>
</html>
<custom-element name="toggle-change-demo">
  <script>
    const editor = useState({ wrap: "Line wrapping is off." });

    function changed(toggle) {
      editor.wrap = toggle.pressed ? "Line wrapping is on." : "Line wrapping is off.";
    }
  </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 variant="outline" onchange="changed(this)">Wrap lines</ui-toggle>
  <p role="status"><slot name="#editor.wrap"></slot></p>
</custom-element>

In a form

A lone toggle adds nothing to FormData. To submit toggles, put them in a ui-toggle-group with a name and give each toggle a value.

Previewindex.htmltoggle-form-demo.html
Lone toggle Pinned
Submit
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toggle 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-form-demo.html"></import-element>
  <toggle-form-demo></toggle-form-demo>
</body>
</html>
<custom-element name="toggle-form-demo">
  <script>
    const result = useState({ text: "Submit the form to see its data." });

    function submit(event) {
      event.preventDefault();
      const entries = [...new FormData(event.target)].map(([name, value]) => `${name}=${value}`);
      result.text = entries.join("\n") || "(nothing submitted)";
    }
  </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;
    }
    .row {
      display: flex;
      gap: calc(var(--spacing) * 2);
    }
    output {
      display: block;
      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)">
    <div class="row">
      <ui-toggle variant="outline" value="draft">Lone toggle</ui-toggle>
      <ui-toggle-group name="flags" type="multiple" aria-label="Flags">
        <ui-toggle variant="outline" value="pinned">Pinned</ui-toggle>
      </ui-toggle-group>
    </div>
    <ui-button type="submit">Submit</ui-button>
  </form>
  <output><slot name="#result.text"></slot></output>
</custom-element>

API

Attributes

Attribute Type Default Description
pressed boolean false Whether the toggle is pressed. Reflects to the attribute.
variant "default" | "outline" "default" Visual style. outline adds a border.
size "default" | "sm" | "lg" "default" Height and padding.
disabled boolean false Disables the button.
value string Identifies the toggle inside a ui-toggle-group.
aria-label string Accessible name, passed to the inner button. Use it for icon-only toggles. Property name: label.

Properties

Property Type Description
pressed boolean Current state. Flipped by each click. Setting it updates the button.

Events

Event Detail Description
change none Fired after each click, once pressed has flipped. Inside a ui-toggle-group, the group stops it and fires its own change.

Slots

Slot Description
(default) The label and icons. Slotted <svg> elements are sized to calc(var(--spacing) * 4).

CSS parts

Part Description
button The inner native <button>.

CSS states and custom properties

Name Description
:state(attr-pressed) Set while the toggle is pressed.
:state(attr-variant-is-outline), :state(attr-size-is-sm), :state(attr-size-is-lg) Set for the current variant and size.
--ui-radius-start, --ui-radius-end Override the start and end corner radii. Set by ui-toggle-group and ui-button-group.

Accessibility

The inner native button has aria-pressed="true" or "false", so screen readers announce it as a toggle button. Enter and Space toggle it, and the host delegates focus to the button.

Edit this page on GitHub · View as Markdown