Skip to content

Toggle

Experimental Alpha

This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.

The Toggle component provides an accessible binary switch for on/off states, implementing the WAI-ARIA Switch pattern.

Examples

Vue
Lit
React
Live Preview

Default Toggles

Sizes

Variants

Interactive Examples

Demonstrates event handling with @toggle-change and v-model:checked

Notifications: Disabled

Dark mode: Off

Form data: name="", value="", checked=false

Toggle above programmatically

Labels, Helper Text, and Validation

The Toggle component supports labels, helper text, required fields, and validation states.

Label Positioning

Control label position with label-position: 'top' (default), 'start', 'end', or 'bottom'.

CSS Shadow Parts Customization

View Vue Code
<template>
  <section>
    <div class="mbe2">
      <h2>Default Toggles</h2>
    </div>
    <div class="stacked-mobile mbe4">
      <VueToggle label="Default toggle" />
      <VueToggle
        label="Checked by default"
        :checked="true"
      />
      <VueToggle
        label="Disabled"
        disabled
      />
      <VueToggle
        label="Disabled and checked"
        :checked="true"
        disabled
      />
    </div>

    <div class="mbe2">
      <h2>Sizes</h2>
    </div>
    <div class="stacked-mobile mbe4">
      <VueToggle
        label="Extra small"
        size="xs"
      />
      <VueToggle
        label="Small"
        size="sm"
      />
      <VueToggle
        label="Medium (default)"
        size="md"
      />
      <VueToggle
        label="Large"
        size="lg"
      />
      <VueToggle
        label="Extra large"
        size="xl"
      />
    </div>

    <div class="mbe2">
      <h2>Variants</h2>
    </div>
    <div class="stacked-mobile mbe4">
      <VueToggle
        label="Default variant"
        :checked="true"
      />
      <VueToggle
        label="Success"
        variant="success"
        :checked="true"
      />
      <VueToggle
        label="Warning"
        variant="warning"
        :checked="true"
      />
      <VueToggle
        label="Danger"
        variant="danger"
        :checked="true"
      />
      <VueToggle
        label="Monochrome"
        variant="monochrome"
        :checked="true"
      />
    </div>

    <div class="mbe2">
      <h2>Interactive Examples</h2>
      <p class="mbs2 mbe3">
        Demonstrates event handling with @toggle-change and v-model:checked
      </p>
    </div>
    <div class="stacked-mobile mbe4">
      <!-- Pattern 1: @toggle-change event -->
      <div>
        <VueToggle
          label="Notifications (@toggle-change)"
          :checked="notificationsEnabled"
          @toggle-change="handleToggleChange"
        />
        <p style="margin-top: 0.5rem">
          Notifications: <strong>{{ notificationsEnabled ? 'Enabled' : 'Disabled' }}</strong>
        </p>
      </div>

      <!-- Pattern 2: v-model:checked (two-way binding) -->
      <div>
        <VueToggle
          label="Dark Mode (v-model:checked)"
          v-model:checked="darkModeEnabled"
        />
        <p style="margin-top: 0.5rem">
          Dark mode: <strong>{{ darkModeEnabled ? 'On' : 'Off' }}</strong>
        </p>
      </div>

      <!-- Pattern 3: Form integration with event detail -->
      <div>
        <VueToggle
          label="Newsletter Subscription"
          name="newsletter"
          value="subscribed"
          :checked="formData.newsletter"
          @toggle-change="handleFormToggle"
        />
        <p style="margin-top: 0.5rem; font-size: 0.875rem; color: var(--ag-text-secondary);">
          Form data: name="{{ formData.name }}", value="{{ formData.value }}", checked={{ formData.newsletter }}
        </p>
      </div>
    </div>
    <div class="mbe4 flex justify-center">
      <VueButton
        @click="darkModeEnabled = !darkModeEnabled"
        mode="primary"
        size="small"
      >
        Toggle above programmatically
      </VueButton>
    </div>

    <div class="mbe4">
      <h2>Labels, Helper Text, and Validation</h2>
      <p class="mbs2 mbe3">
        The Toggle component supports labels, helper text, required fields, and validation states.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 600px;"
    >
      <div class="mbe3">
        <VueToggle label="Enable notifications" />
      </div>

      <div class="mbe3">
        <VueToggle
          label="Dark mode"
          help-text="Reduces eye strain in low-light environments"
        />
      </div>

      <div class="mbe3">
        <VueToggle
          label="Two-factor authentication"
          :required="true"
          help-text="This setting is required for account security"
        />
      </div>

      <div class="mbe3">
        <VueToggle
          label="Email notifications"
          :required="true"
          :invalid="true"
          error-message="You must enable email notifications to continue"
        />
      </div>
    </div>

    <div class="mbe4">
      <h2>Label Positioning</h2>
      <p class="mbs2 mbe3">
        Control label position with <code>label-position</code>: 'top' (default), 'start', 'end', or 'bottom'.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 600px;"
    >
      <div class="mbe3">
        <VueToggle
          label="Top Label (Default)"
          label-position="top"
        />
      </div>

      <div class="mbe3">
        <VueToggle
          label="Start Position"
          label-position="start"
        />
      </div>

      <div class="mbe3">
        <VueToggle
          label="End Position"
          label-position="end"
        />
      </div>

      <div class="mbe3">
        <VueToggle
          label="Bottom Position"
          label-position="bottom"
          help-text="Bottom label position for alternative layouts"
        />
      </div>
    </div>

    <div class="mbe2">
      <h2>CSS Shadow Parts Customization</h2>
    </div>
    <div class="stacked-mobile mbe4">
      <div v-html="customToggleStyles"></div>
      <VueToggle
        class="custom-toggle"
        label="Customized Toggle"
        :checked="true"
      />
    </div>
  </section>
</template>

<script>
import VueToggle from "agnosticui-core/toggle/vue";
import VueButton from "agnosticui-core/button/vue";

export default {
  name: "ToggleExamples",
  components: { VueToggle, VueButton },
  data() {
    return {
      notificationsEnabled: false,
      darkModeEnabled: false,
      formData: {
        newsletter: false,
        name: "",
        value: "",
      },
      customToggleStyles: `
        <style>
          .custom-toggle::part(ag-toggle-button) {
            border: 2px solid var(--ag-coral-50);
            border-radius: 9999px;
            padding: var(--ag-space-2) var(--ag-space-3);
          }
          .custom-toggle::part(ag-toggle-track) {
            background-color: #f0f0f0;
          }
          .custom-toggle::part(ag-toggle-handle) {
            background-color: var(--ag-coral-200);
            border: 2px solid #fff;
            box-shadow: 0 0 5px var(--ag-coral-200);
          }
          .custom-toggle[checked]::part(ag-toggle-track) {
            background-color:var(--ag-coral-300);
          }
          .custom-toggle[checked]::part(ag-toggle-handle) {
            background-color: #fff;
          }
        </style>
      `,
    };
  },
  methods: {
    handleToggleChange(event) {
      this.notificationsEnabled = event.checked;
      console.log("Toggle changed:", event);
    },
    handleFormToggle(event) {
      this.formData.newsletter = event.checked;
      this.formData.name = event.name;
      this.formData.value = event.value;
      console.log("Form toggle changed:", event);
    },
  },
};
</script>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/toggle';
import 'agnosticui-core/button';

export class ToggleLitExamples extends LitElement {
  createRenderRoot() {
    return this;
  }

  static get properties() {
    return {
      notificationsEnabled: { type: Boolean, state: true },
      darkModeEnabled: { type: Boolean, state: true },
      formData: { type: Object, state: true },
    };
  }

  constructor() {
    super();
    this.notificationsEnabled = false;
    this.darkModeEnabled = false;
    this.formData = {
      newsletter: false,
      name: "",
      value: "",
    };
    this.customToggleStyles = html`
      <style>
        .custom-toggle::part(ag-toggle-button) {
          border: 2px solid var(--ag-coral-50);
          border-radius: 9999px;
          padding: var(--ag-space-2) var(--ag-space-3);
        }
        .custom-toggle::part(ag-toggle-track) {
          background-color: #f0f0f0;
        }
        .custom-toggle::part(ag-toggle-handle) {
          background-color: var(--ag-coral-200);
          border: 2px solid #fff;
          box-shadow: 0 0 5px var(--ag-coral-200);
        }
        .custom-toggle[checked]::part(ag-toggle-track) {
          background-color: var(--ag-coral-300);
        }
        .custom-toggle[checked]::part(ag-toggle-handle) {
          background-color: #fff;
        }
      </style>
    `;
  }

  handleToggleChange(event) {
    this.notificationsEnabled = event.detail.checked;
    console.log("Toggle changed:", event.detail);
  }
  
  handleDarkModeToggle(event) {
    this.darkModeEnabled = event.detail.checked;
  }

  handleFormToggle(event) {
    this.formData = {
      newsletter: event.detail.checked,
      name: event.detail.name,
      value: event.detail.value,
    };
    console.log("Form toggle changed:", event.detail);
  }

  toggleDarkModeProgrammatically() {
    this.darkModeEnabled = !this.darkModeEnabled;
  }
  
  render() {
    return html`
      <section>
        <div class="mbe2">
          <h2>Default Toggles</h2>
        </div>
        <div class="stacked-mobile mbe4">
          <ag-toggle label="Default toggle"></ag-toggle>
          <ag-toggle label="Checked by default" checked></ag-toggle>
          <ag-toggle label="Disabled" disabled></ag-toggle>
          <ag-toggle label="Disabled and checked" checked disabled></ag-toggle>
        </div>

        <div class="mbe2">
          <h2>Sizes</h2>
        </div>
        <div class="stacked-mobile mbe4">
          <ag-toggle label="Extra small" size="xs"></ag-toggle>
          <ag-toggle label="Small" size="sm"></ag-toggle>
          <ag-toggle label="Medium (default)" size="md"></ag-toggle>
          <ag-toggle label="Large" size="lg"></ag-toggle>
          <ag-toggle label="Extra large" size="xl"></ag-toggle>
        </div>

        <div class="mbe2">
          <h2>Variants</h2>
        </div>
        <div class="stacked-mobile mbe4">
          <ag-toggle label="Default variant" checked></ag-toggle>
          <ag-toggle label="Success" variant="success" checked></ag-toggle>
          <ag-toggle label="Warning" variant="warning" checked></ag-toggle>
          <ag-toggle label="Danger" variant="danger" checked></ag-toggle>
          <ag-toggle label="Monochrome" variant="monochrome" checked></ag-toggle>
        </div>

        <div class="mbe2">
          <h2>Interactive Examples</h2>
          <p class="mbs2 mbe3">
            Demonstrates event handling with @toggle-change.
          </p>
        </div>
        <div class="stacked-mobile mbe4">
          <div>
            <ag-toggle
              label="Notifications (@toggle-change)"
              .checked=${this.notificationsEnabled}
              @toggle-change=${this.handleToggleChange}
            ></ag-toggle>
            <p style="margin-top: 0.5rem">
              Notifications: <strong>${this.notificationsEnabled ? 'Enabled' : 'Disabled'}</strong>
            </p>
          </div>
          <div>
            <ag-toggle
              label="Dark Mode"
              .checked=${this.darkModeEnabled}
              @toggle-change=${this.handleDarkModeToggle}
            ></ag-toggle>
            <p style="margin-top: 0.5rem">
              Dark mode: <strong>${this.darkModeEnabled ? 'On' : 'Off'}</strong>
            </p>
          </div>
          <div>
            <ag-toggle
              label="Newsletter Subscription"
              name="newsletter"
              value="subscribed"
              .checked=${this.formData.newsletter}
              @toggle-change=${this.handleFormToggle}
            ></ag-toggle>
            <p style="margin-top: 0.5rem; font-size: 0.875rem; color: var(--ag-text-secondary);">
              Form data: name="${this.formData.name}", value="${this.formData.value}", checked=${this.formData.newsletter}
            </p>
          </div>
        </div>
        <div class="mbe4 flex justify-center">
          <ag-button
            @click=${this.toggleDarkModeProgrammatically}
            mode="primary"
            size="small"
          >
            Toggle above programmatically
          </ag-button>
        </div>

        <div class="mbe4">
          <h2>Labels, Helper Text, and Validation</h2>
          <p class="mbs2 mbe3">
            The Toggle component supports labels, helper text, required fields, and validation states.
          </p>
        </div>
        <div class="mbe4" style="max-width: 600px;">
          <div class="mbe3">
            <ag-toggle label="Enable notifications"></ag-toggle>
          </div>
          <div class="mbe3">
            <ag-toggle
              label="Dark mode"
              help-text="Reduces eye strain in low-light environments"
            ></ag-toggle>
          </div>
          <div class="mbe3">
            <ag-toggle
              label="Two-factor authentication"
              required
              help-text="This setting is required for account security"
            ></ag-toggle>
          </div>
          <div class="mbe3">
            <ag-toggle
              label="Email notifications"
              required
              invalid
              error-message="You must enable email notifications to continue"
            ></ag-toggle>
          </div>
        </div>

        <div class="mbe4">
          <h2>Label Positioning</h2>
          <p class="mbs2 mbe3">
            Control label position with <code>label-position</code>: 'top' (default), 'start', 'end', or 'bottom'.
          </p>
        </div>
        <div class="mbe4" style="max-width: 600px;">
          <div class="mbe3">
            <ag-toggle label="Top Label (Default)" label-position="top"></ag-toggle>
          </div>
          <div class="mbe3">
            <ag-toggle label="Start Position" label-position="start"></ag-toggle>
          </div>
          <div class="mbe3">
            <ag-toggle label="End Position" label-position="end"></ag-toggle>
          </div>
          <div class="mbe3">
            <ag-toggle
              label="Bottom Position"
              label-position="bottom"
              help-text="Bottom label position for alternative layouts"
            ></ag-toggle>
          </div>
        </div>

        <div class="mbe2">
          <h2>CSS Shadow Parts Customization</h2>
        </div>
        <div class="stacked-mobile mbe4">
          ${this.customToggleStyles}
          <ag-toggle
            class="custom-toggle"
            label="Customized Toggle"
            checked
          ></ag-toggle>
        </div>
      </section>
    `;
  }
}
customElements.define('toggle-lit-examples', ToggleLitExamples);

Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.

View React Code
import { useState } from "react";
import { ReactToggle } from "agnosticui-core/toggle/react";
import { ReactButton } from "agnosticui-core/button/react";

export default function ToggleReactExamples() {
  const [notificationsEnabled, setNotificationsEnabled] = useState(false);
  const [darkModeEnabled, setDarkModeEnabled] = useState(false);
  const [formData, setFormData] = useState({
    newsletter: false,
    name: "",
    value: "",
  });

  const handleToggleChange = (event) => {
    setNotificationsEnabled(event.checked);
    console.log("Toggle changed:", event);
  };

  const handleFormToggle = (event) => {
    setFormData({
      newsletter: event.checked,
      name: event.name,
      value: event.value,
    });
    console.log("Form toggle changed:", event);
  };
  
  const customToggleStyles = `
    <style>
      .custom-toggle::part(ag-toggle-button) {
        border: 2px solid var(--ag-coral-50);
        border-radius: 9999px;
        padding: var(--ag-space-2) var(--ag-space-3);
      }
      .custom-toggle::part(ag-toggle-track) {
        background-color: #f0f0f0;
      }
      .custom-toggle::part(ag-toggle-handle) {
        background-color: var(--ag-coral-200);
        border: 2px solid #fff;
        box-shadow: 0 0 5px var(--ag-coral-200);
      }
      .custom-toggle[checked]::part(ag-toggle-track) {
        background-color:var(--ag-coral-300);
      }
      .custom-toggle[checked]::part(ag-toggle-handle) {
        background-color: #fff;
      }
    </style>
  `;

  return (
    <section>
      <div className="mbe2">
        <h2>Default Toggles</h2>
      </div>
      <div className="stacked-mobile mbe4">
        <ReactToggle label="Default toggle" />
        <ReactToggle
          label="Checked by default"
          checked={true}
        />
        <ReactToggle
          label="Disabled"
          disabled
        />
        <ReactToggle
          label="Disabled and checked"
          checked={true}
          disabled
        />
      </div>

      <div className="mbe2">
        <h2>Sizes</h2>
      </div>
      <div className="stacked-mobile mbe4">
        <ReactToggle
          label="Extra small"
          size="xs"
        />
        <ReactToggle
          label="Small"
          size="sm"
        />
        <ReactToggle
          label="Medium (default)"
          size="md"
        />
        <ReactToggle
          label="Large"
          size="lg"
        />
        <ReactToggle
          label="Extra large"
          size="xl"
        />
      </div>

      <div className="mbe2">
        <h2>Variants</h2>
      </div>
      <div className="stacked-mobile mbe4">
        <ReactToggle
          label="Default variant"
          checked={true}
        />
        <ReactToggle
          label="Success"
          variant="success"
          checked={true}
        />
        <ReactToggle
          label="Warning"
          variant="warning"
          checked={true}
        />
        <ReactToggle
          label="Danger"
          variant="danger"
          checked={true}
        />
        <ReactToggle
          label="Monochrome"
          variant="monochrome"
          checked={true}
        />
      </div>

      <div className="mbe2">
        <h2>Interactive Examples</h2>
        <p className="mbs2 mbe3">
          Demonstrates event handling with onToggleChange.
        </p>
      </div>
      <div className="stacked-mobile mbe4">
        <div>
          <ReactToggle
            label="Notifications (onToggleChange)"
            checked={notificationsEnabled}
            onToggleChange={handleToggleChange}
          />
          <p style={{ marginTop: "0.5rem" }}>
            Notifications: <strong>{notificationsEnabled ? 'Enabled' : 'Disabled'}</strong>
          </p>
        </div>
        <div>
          <ReactToggle
            label="Dark Mode"
            checked={darkModeEnabled}
            onToggleChange={(e) => setDarkModeEnabled(e.checked)}
          />
          <p style={{ marginTop: "0.5rem" }}>
            Dark mode: <strong>{darkModeEnabled ? 'On' : 'Off'}</strong>
          </p>
        </div>
        <div>
          <ReactToggle
            label="Newsletter Subscription"
            name="newsletter"
            value="subscribed"
            checked={formData.newsletter}
            onToggleChange={handleFormToggle}
          />
          <p style={{ marginTop: "0.5rem", fontSize: "0.875rem", color: "var(--ag-text-secondary)" }}>
            Form data: name="{formData.name}", value="{formData.value}", checked={String(formData.newsletter)}
          </p>
        </div>
      </div>
      <div className="mbe4 flex justify-center">
        <ReactButton
          onClick={() => setDarkModeEnabled(!darkModeEnabled)}
          mode="primary"
          size="small"
        >
          Toggle above programmatically
        </ReactButton>
      </div>

      <div className="mbe4">
        <h2>Labels, Helper Text, and Validation</h2>
        <p className="mbs2 mbe3">
          The Toggle component supports labels, helper text, required fields, and validation states.
        </p>
      </div>
      <div
        className="mbe4"
        style={{ maxWidth: "600px" }}
      >
        <div className="mbe3">
          <ReactToggle label="Enable notifications" />
        </div>

        <div className="mbe3">
          <ReactToggle
            label="Dark mode"
            helpText="Reduces eye strain in low-light environments"
          />
        </div>

        <div className="mbe3">
          <ReactToggle
            label="Two-factor authentication"
            required={true}
            helpText="This setting is required for account security"
          />
        </div>

        <div className="mbe3">
          <ReactToggle
            label="Email notifications"
            required={true}
            invalid={true}
            errorMessage="You must enable email notifications to continue"
          />
        </div>
      </div>

      <div className="mbe4">
        <h2>Label Positioning</h2>
        <p className="mbs2 mbe3">
          Control label position with <code>labelPosition</code>: 'top' (default), 'start', 'end', or 'bottom'.
        </p>
      </div>
      <div
        className="mbe4"
        style={{ maxWidth: "600px" }}
      >
        <div className="mbe3">
          <ReactToggle
            label="Top Label (Default)"
            labelPosition="top"
          />
        </div>

        <div className="mbe3">
          <ReactToggle
            label="Start Position"
            labelPosition="start"
          />
        </div>

        <div className="mbe3">
          <ReactToggle
            label="End Position"
            labelPosition="end"
          />
        </div>

        <div className="mbe3">
          <ReactToggle
            label="Bottom Position"
            labelPosition="bottom"
            helpText="Bottom label position for alternative layouts"
          />
        </div>
      </div>

      <div className="mbe2">
        <h2>CSS Shadow Parts Customization</h2>
      </div>
      <div className="stacked-mobile mbe4">
        <div dangerouslySetInnerHTML={{ __html: customToggleStyles }}></div>
        <ReactToggle
          className="custom-toggle"
          label="Customized Toggle"
          checked={true}
        />
      </div>
    </section>
  );
}
Open in StackBlitz

Props

PropTypeDefaultDescription
labelstring''Label text for the toggle
labelPosition'top' | 'start' | 'end' | 'bottom''top'Position of the label relative to the toggle
labelHiddenbooleanfalseVisually hide the label (still accessible to screen readers)
noLabelbooleanfalseRemove label completely
checkedbooleanfalseWhether the toggle is checked/on
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Size of the toggle switch
variant'default' | 'success' | 'warning' | 'danger' | 'monochrome''default'Color variant when checked
disabledbooleanfalseWhether the toggle is disabled
readonlybooleanfalseToggle appears interactive but cannot be changed
requiredbooleanfalseRequired field indicator
invalidbooleanfalseInvalid state for validation feedback
errorMessagestring''Error message text displayed when invalid
helpTextstring''Helper text displayed below toggle
namestring''Name for form integration
valuestring''Value when toggle is checked (for forms)

Events

EventFrameworkDetailDescription
clickVue: @click
React: onClick
Lit: @click
MouseEventFired when the toggle is clicked.
toggle-changeVue: @toggle-change
React: onToggleChange
Lit: @toggle-change
{ checked: boolean, name: string, value: string }Fired when toggle state changes. Contains the new checked state and form integration data.

Note: The Toggle component supports dual-dispatch event propagation: it dispatches both DOM CustomEvents (usable with addEventListener) and invokes callback props (.onToggleChange), giving you flexibility in how you handle events.

Type:

ts
export type ToggleChangeEvent = CustomEvent<ToggleChangeEventDetail>;

export interface ToggleChangeEventDetail {
  checked: boolean;  // New checked state
  name: string;      // Form name (if provided)
  value: string;     // Form value (if provided)
}

Event Handling Patterns

Lit / Web Components
typescript
// Pattern 1: addEventListener (DOM event)
const toggle = document.querySelector('ag-toggle');
toggle.addEventListener('toggle-change', (e) => {
  console.log('Checked:', e.detail.checked);
  console.log('Form name:', e.detail.name);
  console.log('Form value:', e.detail.value);
});

// Pattern 2: Callback prop
html`<ag-toggle
  label="Enable feature"
  .onToggleChange=${(e: CustomEvent) => {
    console.log('Callback fired:', e.detail);
  }}
></ag-toggle>`

// Pattern 3: Both (dual-dispatch)
html`<ag-toggle
  label="With both"
  @toggle-change=${(e) => console.log('DOM event')}
  .onToggleChange=${(e) => console.log('Callback')}
></ag-toggle>`
React
tsx
import { ReactToggle } from 'agnosticui-core/toggle/react';

// React automatically maps toggle-change to onToggleChange
<ReactToggle
  label="Enable notifications"
  onToggleChange={(e) => {
    console.log('Checked:', e.detail.checked);
    console.log('Name:', e.detail.name);
    console.log('Value:', e.detail.value);
  }}
  onClick={(e) => {
    console.log('Native click also available:', e);
  }}
/>
Vue
vue
<template>
  <!-- Pattern 1: @toggle-change event -->
  <VueToggle
    label="Enable feature"
    @toggle-change="handleToggle"
  />

  <!-- Pattern 2: v-model:checked (two-way binding) -->
  <VueToggle
    label="Dark mode"
    v-model:checked="isDarkMode"
  />

  <!-- Pattern 3: Both together -->
  <VueToggle
    label="With both"
    v-model:checked="isEnabled"
    @toggle-change="handleToggle"
  />
</template>

<script setup>
import { ref } from 'vue';

const isDarkMode = ref(false);
const isEnabled = ref(false);

// Event emits detail object (not full CustomEvent)
const handleToggle = (detail) => {
  console.log('Checked:', detail.checked);
  console.log('Name:', detail.name);
  console.log('Value:', detail.value);
};
</script>

Event Detail Structure

The toggle-change event provides the following detail payload:

typescript
interface ToggleChangeEventDetail {
  checked: boolean;  // New checked state
  name: string;      // Form name (if provided)
  value: string;     // Form value (if provided)
}

Accessibility

The Toggle component implements the WAI-ARIA Switch pattern:

  • Uses semantic <button> element with role="switch"
  • Communicates state via aria-checked
  • Requires accessible name via label or aria-label prop
  • Keyboard accessible (Space/Enter to toggle)
  • Screen reader announces "On"/"Off" state
  • Minimum 44px touch target for mobile accessibility
  • Clear focus indicators for keyboard navigation

Form Integration

Use the name and value props to integrate with forms:

Vue
vue
<template>
  <form @submit.prevent="handleSubmit">
    <VueToggle
      label="Subscribe to newsletter"
      name="newsletter"
      value="yes"
      :checked="formData.newsletter"
      @toggle-change="handleChange"
    />
    <VueButton type="submit">Submit</VueButton>
  </form>
</template>

<script>
export default {
  data() {
    return {
      formData: {
        newsletter: false,
      },
    };
  },
  methods: {
    handleChange(event) {
      this.formData.newsletter = event.checked;
    },
    handleSubmit() {
      console.log('Submit:', this.formData);
    },
  },
};
</script>
React
tsx
import { useState } from 'react';

export default function Form() {
  const [formData, setFormData] = useState({ newsletter: false });

  const handleChange = (event: CustomEvent) => {
    setFormData({ newsletter: event.detail.checked });
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('Submit:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <ReactToggle
        label="Subscribe to newsletter"
        name="newsletter"
        value="yes"
        checked={formData.newsletter}
        onToggleChange={handleChange}
      />
      <button type="submit">Submit</button>
    </form>
  );
}
Lit (Web Components)
html
<form id="myForm">
  <ag-toggle
    id="newsletter-toggle"
    label="Subscribe to newsletter"
    name="newsletter"
    value="yes"
  ></ag-toggle>
  <button type="submit">Submit</button>
</form>

<script type="module">
  let formData = { newsletter: false };

  const toggle = document.querySelector('#newsletter-toggle');
  toggle?.addEventListener('toggle-change', (event) => {
    formData.newsletter = event.detail.checked;
  });

  document.querySelector('#myForm')?.addEventListener('submit', (e) => {
    e.preventDefault();
    console.log('Submit:', formData);
  });
</script>

CSS Shadow Parts

PartDescription
ag-toggle-buttonThe main button element for the toggle.
ag-toggle-trackThe track of the toggle switch.
ag-toggle-handleThe handle of the toggle switch.

Example

css
.custom-toggle::part(ag-toggle-button) {
  border: 2px solid #bada55;
  border-radius: 9999px;
}
.custom-toggle::part(ag-toggle-track) {
  background-color: #f0f0f0;
}
.custom-toggle::part(ag-toggle-handle) {
  background-color: #bada55;
  border: 2px solid #fff;
  box-shadow: 0 0 5px #bada55;
}
.custom-toggle[checked]::part(ag-toggle-track) {
  background-color: #bada55;
}
.custom-toggle[checked]::part(ag-toggle-handle) {
  background-color: #fff;
}