Toggle

A single-select toggle group that allows the user to choose one option from a set. Each option can display text or an icon.

import { Toggle } from "@tenstorrent/vesper/toggle";

export default function ToggleDemo() {
  return (
    <Toggle
      defaultValue="monthly"
      options={[
        { value: "monthly", text: "Monthly" },
        { value: "yearly", text: "Yearly" },
      ]}
    />
  );
}
Always give a Toggle an accessible name by either pairing it with a <label> via htmlFor or giving it an aria-label. Without one, assistive technology announces the field with no indication of what it is for.

Options

PropTypeDescriptionDefault
optionsToggleOption[]The list of toggle options. Each has value and either text or icon (with required ariaLabel).—
size"sm" | "md" | "lg"The size of the toggle and its options."md"
valuestringThe currently selected value (controlled mode).—
defaultValuestringThe initially selected value (uncontrolled mode).—
onValueChange(value: string) => voidCallback fired when the selected value changes.—
disabledbooleanWhen true, disables all toggle options.false
namestringThe name of the underlying select, used as the field name when submitted with form data.—
requiredbooleanWhen true, makes the underlying input required when rendered inside of a form.false

Any other props are forwarded to the wrapping <div> element.

ToggleOption

PropTypeDescriptionDefault
valuestringA unique value identifying this toggle option.—
textstringThe text label displayed in the toggle option.—
iconReactNodeAn icon element displayed instead of text.—
ariaLabelstringAn accessible aria-label. Required for icon-only options.—

Examples

Controlled vs uncontrolled

Render a Toggle in an uncontrolled fashion to let it keep track of its own state. Pass defaultValue if it should start out with a value pre-selected:

import { Grid, List } from "@tenstorrent/vesper/icons";
import { Toggle } from "@tenstorrent/vesper/toggle";

export default function UncontrolledToggle() {
  return (
    <Toggle
      aria-label="Display options"
      defaultValue="grid"
      options={[
        { value: "grid", icon: <Grid />, ariaLabel: "Grid view" },
        { value: "list", icon: <List />, ariaLabel: "List view" },
      ]}
    />
  );
}

If you need to control the toggle's state, you can do so via the value and onValueChange props:

Value: nothing selected

import { useState } from "react";

import { Grid, List } from "@tenstorrent/vesper/icons";
import { Toggle } from "@tenstorrent/vesper/toggle";
import { Typography } from "@tenstorrent/vesper/typography";

export default function ControlledToggle() {
  const [value, setValue] = useState("");

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: "var(--vesper-spacing-4)",
      }}
    >
      <Toggle
        aria-label="Display options"
        value={value}
        onValueChange={setValue}
        options={[
          { value: "list", icon: <List />, ariaLabel: "List view" },
          { value: "grid", icon: <Grid />, ariaLabel: "Grid view" },
        ]}
      />
      <Typography variant="copy-sm">
        Value: {value || "nothing selected"}
      </Typography>
    </div>
  );
}

Text and icon options

You can render both text and icon options in a Toggle. To render text options, you would pass an array of options objects that each have value and text properties:

import { Toggle } from "@tenstorrent/vesper/toggle";

export default function TextOptionsToggle() {
  return (
    <Toggle
      aria-label="Membership tier"
      options={[
        { value: "free", text: "Free" },
        { value: "basic", text: "Basic" },
        { value: "pro", text: "Pro" },
      ]}
      defaultValue="free"
    />
  );
}

To render icon options, pass an array of options objects that each have value, icon, and ariaLabel properties:

import { Globe, Lock } from "@tenstorrent/vesper/icons";
import { Toggle } from "@tenstorrent/vesper/toggle";

export default function IconOptionsToggle() {
  return (
    <Toggle
      aria-label="Project visibility"
      options={[
        { value: "public", icon: <Globe />, ariaLabel: "Public" },
        { value: "private", icon: <Lock />, ariaLabel: "Private" },
      ]}
      defaultValue="public"
    />
  );
}

Different sizes

Toggle can be rendered in one of three sizes; "sm", "md", or "lg". The default size is "md".

import { Toggle } from "@tenstorrent/vesper/toggle";

export default function AllToggleSizes() {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: "var(--vesper-spacing-4)",
      }}
    >
      <Toggle
        size="sm"
        defaultValue="monthly"
        aria-label="Billing period"
        options={[
          { value: "monthly", text: "Monthly" },
          { value: "yearly", text: "Yearly" },
        ]}
      />
      <Toggle
        size="md"
        defaultValue="monthly"
        aria-label="Billing period"
        options={[
          { value: "monthly", text: "Monthly" },
          { value: "yearly", text: "Yearly" },
        ]}
      />
      <Toggle
        size="lg"
        defaultValue="monthly"
        aria-label="Billing period"
        options={[
          { value: "monthly", text: "Monthly" },
          { value: "yearly", text: "Yearly" },
        ]}
      />
    </div>
  );
}

Required fields

Passing required marks the toggle as required for native form validation:

import { Toggle } from "@tenstorrent/vesper/toggle";

export default function RequiredToggleDemo() {
  return (
    <Toggle
      required
      name="billing_period"
      aria-label="Billing period"
      defaultValue="monthly"
      options={[
        { value: "weekly", text: "Weekly" },
        { value: "monthly", text: "Monthly" },
        { value: "yearly", text: "Yearly" },
      ]}
    />
  );
}

Disabling the toggle

You can disable a Toggle by passing disabled={true} or just disabled as a prop. A disabled toggle still displays its current state, but it cannot be toggled by pointer or keyboard, is skipped in the tab order, and is excluded from form data:

import { Toggle } from "@tenstorrent/vesper/toggle";

export default function DisabledToggle() {
  return (
    <Toggle
      disabled
      name="billing_period"
      aria-label="Billing period"
      defaultValue="monthly"
      options={[
        { value: "weekly", text: "Weekly" },
        { value: "monthly", text: "Monthly" },
        { value: "yearly", text: "Yearly" },
      ]}
    />
  );
}

Usage in forms

Toggle renders a native <select>, so it works with regular form submission and native validation via props like name, value, form, and required.

Pass required when the toggle must have an option selected before the form can be submitted, such as a terms and conditions agreement:

import { useState } from "react";

import { Button } from "@tenstorrent/vesper/button";
import { Toggle } from "@tenstorrent/vesper/toggle";
import { Typography } from "@tenstorrent/vesper/typography";

export default function FormToggleDemo() {
  const [submitted, setSubmitted] = useState<string>("");

  return (
    <form
      style={{
        display: "flex",
        flexDirection: "column",
        gap: "var(--vesper-spacing-4)",
      }}
      onSubmit={(event) => {
        event.preventDefault();
        const data = new FormData(event.currentTarget);
        console.log(Object.fromEntries(data.entries()));
        setSubmitted(String(data.get("billing_period") ?? ""));
      }}
    >
      <Toggle
        name="billing_period"
        aria-label="Billing period"
        options={[
          { value: "weekly", text: "Weekly" },
          { value: "monthly", text: "Monthly" },
          { value: "yearly", text: "Yearly" },
        ]}
      />
      <Button size="sm" type="submit">
        Submit
      </Button>
      {!!submitted && (
        <Typography variant="copy-sm">Submitted value: {submitted}</Typography>
      )}
    </form>
  );
}

The form above submits billing_period=monthly when the "Monthly" toggle option is selected, and refuses to submit at all while the toggle is empty.

Use the form prop to associate the field with a <form> rendered elsewhere on the page:

import { Button } from "@tenstorrent/vesper/button";
import { Toggle } from "@tenstorrent/vesper/toggle";

export default function ToggleFormPropDemo() {
  return (
    <div>
      <form id="signup" action="/api/signup" method="post">
        <Button type="submit">Create account</Button>
      </form>
      {/* rendered outside of the form, but submitted with it */}
      <Toggle
        required
        form="signup"
        name="billing_period"
        aria-label="Billing period"
        options={[
          { value: "weekly", text: "Weekly" },
          { value: "monthly", text: "Monthly" },
          { value: "yearly", text: "Yearly" },
        ]}
      />
    </div>
  );
}

Accessing the underlying element

Use the ref prop when you need direct access to the underlying <div> element:

import { useRef } from "react";
import { Toggle } from "@tenstorrent/vesper/toggle";

export default function ToggleRefDemo() {
  const ref = useRef<HTMLDivElement>(null);

  return (
    <Toggle
      ref={ref}
      name="billing_period"
      aria-label="Billing period"
      options={[
        { value: "weekly", text: "Weekly" },
        { value: "monthly", text: "Monthly" },
        { value: "yearly", text: "Yearly" },
      ]}
    />
  );
}