OTTODesign System

Code

Switch

Storybook group: Components · Sidebar path: Components/Switch · Extracted 28.09.2026

Version Tag Status API
v3 <oc-switch-v3> Stable, allowed for generation SwitchV3
v2 <oc-switch-v2> Deprecated, NOT allowed for generation SwitchV2

Only the latest version (v3) is allowed for generation. Older versions are kept for reference and are deprecated.

Overview (v3)

Source: ./src/components/switch/v3/Overview.mdx

Switch

The switch component toggles a binary state such as notifications, privacy settings, or feature flags. It exposes a slotted input type="checkbox" for native form participation and an optional label slot for context.

Default variation

Story Default:

<oc-switch-v3><input type='checkbox' aria-label='Switch purpose' /></oc-switch-v3>

Configuration

The switch component is available in the main styling variants .. This component is configurable, allowing you to tailor its features and appearance to your specific needs. To explore all the available options and adjust the component, use the component configurator and see the changes affect the component in real-time.

Usage guidelines

Before integrating the switch component into your project, make sure you have correctly installed the OTTO components package. Look through the variations page for examples of possible component variations. Here, you can discover both common and specific variations that address different use cases.

Accessibility

The switch component depends on the attributes applied to the slotted input, so include either visible text via the default slot or an aria-label on the input. Propagate disabled, checked, and required to the native control, and expose aria-live messaging externally when relying on the loading state. Reference the built-in accessibility features guide for keyboard support.

Configuration (v3)

Source: ./src/components/switch/v3/Configuration.mdx

Switch configuration

Configure the switch component with the controls below and see the changes live in the preview canvas. Click the Show code button within the preview canvas to see the source code for the current component configuration.

Story Default:

<oc-switch-v3><input type='checkbox' aria-label='Switch purpose' /></oc-switch-v3>

Interactive configurator (Storybook controls); every option is listed in the API section of this file.

Migration (v3)

Source: ./src/components/switch/v3/Migration.mdx

Migration from switch v2 to v3

The oc-switch component has been updated from oc-switch-v2 to oc-switch-v3. This migration guide provides step-by-step instructions to update your project to the latest version.

Skip to:

What's new

Native input element

The component now uses a native <input type="checkbox"> element, providing:

  • Better form integration and native form submission
  • Native validation support
  • Improved accessibility with screen readers
  • Support for standard input attributes like autofocus, form, formnovalidate
Events

All native checkbox events are now available directly on the input element:

  • click
  • input
  • change
  • focus
  • blur
Flexible label content

The label slot allows for more flexible label content, including HTML elements:

<oc-switch-v3>
  <input type="checkbox" name="feature" aria-label="oc-auto" />
  <label slot="label">
    Enable feature <span class="oc-text-color-secondary">(recommended)</span>
  </label>
</oc-switch-v3>

API changes

Changed attributes
v2 Attribute v3 Attribute Notes
fit-content width-behavior="fit" Default changed from fill to "fit" in v3
label-placement label-placement No change
loading loading No change
size size No change
Changed slots
v2 Slot v3 Slot Notes
default (label text) label Label text now uses the label slot
— default Now contains the native <input type="checkbox"> element
Removed attributes

The following attributes have been removed and should now be set on the native <input> element:

v2 Attribute v3 Equivalent Notes
checked Set checked on the <input> element Moved to native input
disabled Set disabled on the <input> element Moved to native input
name Set name on the <input> element Moved to native input
value Set value on the <input> element Moved to native input
oc-aria-label Set aria-label on the <input> element Renamed to standard attribute

How to migrate

The main change is that v3 uses a native <input type="checkbox"> element in the default slot instead of managing the switch state internally. This provides better accessibility, form integration, and native event support.

Migrate a basic switch
<!-- From: -->
<oc-switch-v2 name="notifications" value="on" checked>Enable notifications</oc-switch-v2>
<!-- To: -->
<oc-switch-v3>
  <input type="checkbox" name="notifications" value="on" checked aria-label="oc-auto" />
  <label slot="label">Enable notifications</label>
</oc-switch-v3>
Migrate a switch without a visible label
<!-- From: -->
<oc-switch-v2 oc-aria-label="Toggle dark mode"></oc-switch-v2>
<!-- To: -->
<oc-switch-v3>
  <input type="checkbox" aria-label="Toggle dark mode" />
</oc-switch-v3>
Migrate a switch with label placement
<!-- From: -->
<oc-switch-v2 label-placement="left">Switch label</oc-switch-v2>
<!-- To: -->
<oc-switch-v3 label-placement="left">
  <input type="checkbox" aria-label="oc-auto" />
  <label slot="label">Switch label</label>
</oc-switch-v3>
Migrate a switch with a disabled state
<!-- From: -->
<oc-switch-v2 disabled>Disabled switch</oc-switch-v2>
<!-- To: -->
<oc-switch-v3>
  <input type="checkbox" disabled aria-label="oc-auto" />
  <label slot="label">Disabled switch</label>
</oc-switch-v3>
Migrate a switch with width behavior
<!-- From: -->
<oc-switch-v2 fit-content>Fit content</oc-switch-v2>
<!-- To: -->
<oc-switch-v3 width-behavior="fit">
  <input type="checkbox" aria-label="oc-auto" />
  <label slot="label">Fit content</label>
</oc-switch-v3>

Note In v3, the default value for width-behavior is "fit" instead of "fill".

Migrate a switch with a loading state

The loading attribute remains on the component, not on the input element:

<!-- From: -->
<oc-switch-v2 loading>Processing...</oc-switch-v2>
<!-- To: -->
<oc-switch-v3 loading>
  <input type="checkbox" aria-label="oc-auto" />
  <label slot="label">Processing...</label>
</oc-switch-v3>

API v2 (v2, deprecated, not for generation)

Source: ./src/components/switch/v2/SwitchV2.API.g.mdx

Switch v2 API

API: <oc-switch-v2> (SwitchV2)

The switch component provides a toggle functionality. It has attributes for setting the initial checked state, a disabled state, the value to submit if checked, the name of the input element, the label placement, and the size of the switch. The switch can be initialized as checked or unchecked, and it has a loading state that can be toggled on or off.

Attributes / properties
Attribute Type Default Required Description
size "50" | "100" "100" no Sets the size of the switch.
value string "on" no Sets the initial value of the switch to the provided value for form processing. This is also the reset value for a form reset. If omitted, the default value for the switch is on.
checked boolean false no Toggles the state of the switch between checked and unchecked. Set to true to enable the checked state.

Accessible in CSS via the custom-state mixin.
disabled boolean false no Disables the switch, preventing user interaction. Set to true to disable the switch.
loading boolean false no Toggles the state of the button between loading and not loading. Set to true to enable the loading state and display a loading animation.
fit-content boolean false no Sets the width of the switch to fit its content.
name string no Sets the name to identify the switch when submitting a form.
label-placement "left" | "right" "right" no Sets the placement direction of the label provided via the default slot relative to the switch.
oc-aria-label string undefined no Sets the ARIA label of the switch.
Slots
Slot Required Description
default no Sets the label of the switch element. Change the location of the label by setting the label-placement attribute. By default it will appear on the right side of the switch.
Events
Event Detail type Description
oc-property-change OcSwitchV2Events["oc-property-change"] Whenever a property value changes, this event triggers. Use this event to track all property changes within the component.

Refer to the Events documentation for more information.
oc-mount { component: string; } Fired when the component is mounted to the DOM. The event is fired when the onMount hook of the component is called by the runtime.
oc-unmount { component: string; } Fired when the component is unmounted from the DOM. The event is fired when the function returned by the onMount hook of the component is called by the runtime.

API v3

Source: ./src/components/switch/v3/SwitchV3.API.g.mdx

Switch v3 API

API: <oc-switch-v3> (SwitchV3)

The Switch component allows users to toggle between two states (on and off). Provides configurable label placement, size, loading state, and slots for the label and input element.

Attributes / properties
Attribute Type Default Required Description
size "50" | "100" "100" no The size of the switch.
loading boolean false no Indicates whether the switch displays a loading state. When true, shows a loading animation. When false, displays the normal switch state.
width-behavior "fill" | "fit" "fit" no Defines the width behavior of the switch component. - "fill": The component expands to fill the available width. - "fit": The component width fits its content.
label-placement "left" | "right" "right" no The placement direction of the label provided via the default slot relative to the switch indicator.
Slots
Slot Required Description
default yes Requires one of the following interactive elements: - <input type="checkbox"> for multi-selection - <input type="radio"> for single-selection

For more information on interactive components, refer to the interactive component slots documentation.
label no The label text of the switch element. The label location can be changed using the label-placement attribute. By default, the label appears on the right side of the switch.
Events
Event Detail type Description
oc-property-change OcSwitchV3Events["oc-property-change"] Whenever a property value changes, this event triggers. Use this event to track all property changes within the component.

Refer to the Events documentation for more information.
oc-mount { component: string; } Fired when the component is mounted to the DOM. The event is fired when the onMount hook of the component is called by the runtime.
oc-unmount { component: string; } Fired when the component is unmounted from the DOM. The event is fired when the function returned by the onMount hook of the component is called by the runtime.

Variations (v3)

Source: ./src/components/switch/v3/Variations.mdx

Variations

Listed below are the most common variations of the switch component as well as specific component variations for different use cases.

You can explore all available options using the component configurator, adjust the component to your needs, and see the changes live in a preview canvas.

Default

Story: components-switch-variations--default · tags: components, switch

The standard configuration with size 100 and no visible label. Uses the aria-label attribute on the input element to describe the switch purpose.

Args: defaultSlot=<input type='checkbox' aria-label='Switch purpose' />, labelSlot=``

<oc-switch-v3><input type='checkbox' aria-label='Switch purpose' /></oc-switch-v3>

With label

Story: components-switch-variations--with-label · tags: components, switch

Displays a visible label passed via the default slot. The label-placement attribute controls whether the label appears on the left or right side of the switch.

<oc-switch-v3>
  <label slot="label">Switch label</label>
  <input type='checkbox' aria-label='oc-auto'/>
</oc-switch-v3>

Disabled

Story: components-switch-variations--disabled · tags: components, switch

The disabled state prevents user interaction.

Args: defaultSlot=<input type='checkbox' aria-label='oc-auto' disabled/>

<oc-switch-v3>
  <label slot="label">Switch label</label>
  <input type='checkbox' aria-label='oc-auto' disabled/>
</oc-switch-v3>

Demo: loading state

Story: components-switch-variations--demo-loading-state · tags: components, switch

Demonstrates the loading state when toggling the switch.

<oc-switch-v3 label-placement="left">
  <input type="checkbox" aria-label="oc-auto" />
  <label slot="label">Click me</label>
</oc-switch-v3>

<script>
  (() => {
    const [switchElement] = document.getElementsByTagName("oc-switch-v3");
    const [input] = document.getElementsByTagName("input");
    input.addEventListener("click", () => {
      if (switchElement.loading) {
        return;
      }

      switchElement.loading = true;
      setTimeout(() => {
        switchElement.loading = false;
      }, 1000);
    });
  })();
</script>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: loading state",
  argTypes: hideControlsBadge(Metadata),
  parameters: {
    controls: {
      disabled: true
    },
    // Disables Chromatic's snapshotting, as the spinner is animated und thus leads to diffs
    chromatic: {
      disableSnapshot: true
    }
  },
  render: () => {
    return html` <oc-switch-v3 label-placement="left">
        <input type="checkbox" aria-label="oc-auto" />
        <label slot="label">Click me</label>
      </oc-switch-v3>

      <script>
        (() => {
          const [switchElement] = document.getElementsByTagName("oc-switch-v3");
          const [input] = document.getElementsByTagName("input");
          input.addEventListener("click", () => {
            if (switchElement.loading) {
              return;
            }

            switchElement.loading = true;
            setTimeout(() => {
              switchElement.loading = false;
            }, 1000);
          });
        })();
      </script>`;
  }
}

Demo: form with switch

Story: components-switch-variations--demo-form · tags: components, switch

A demonstration form showcasing the switch component. Logs form data to the console and displays an alert with the submitted data.

<div>
  <form>
    <oc-switch-v3 label-placement="left">
      <input type="checkbox" name="test" aria-label="oc-auto" />
      <label slot="label">Switch label</label>
    </oc-switch-v3>
    <br />
    <oc-switch-v3 label-placement="left">
      <input type="checkbox" name="test" aria-label="oc-auto" disabled />
      <label slot="label">Disabled switch</label>
    </oc-switch-v3>
    <br />
    <oc-button-v1 type="submit">Submit</oc-button-v1>
  </form>
</div>
<script>
  (() => {
    const [form] = document.getElementsByTagName("form");

    form.addEventListener("submit", (ev) => {
      let formData = new FormData(form);
      let data = "";
      formData.forEach((value, key) => (data += key + "=" + value + "\\n"));
      console.log(data);
      alert("sending:\\n" + data);
      ev.preventDefault();
    });
  })();
</script>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: form with switch",
  argTypes: hideControlsBadge(Metadata),
  parameters: {
    controls: {
      disabled: true
    }
  },
  render: () => html`<div>
        <form>
          <oc-switch-v3 label-placement="left">
            <input type="checkbox" name="test" aria-label="oc-auto" />
            <label slot="label">Switch label</label>
          </oc-switch-v3>
          <br />
          <oc-switch-v3 label-placement="left">
            <input type="checkbox" name="test" aria-label="oc-auto" disabled />
            <label slot="label">Disabled switch</label>
          </oc-switch-v3>
          <br />
          <oc-button-v1 type="submit">Submit</oc-button-v1>
        </form>
      </div>
      <script>
        (() => {
          const [form] = document.getElementsByTagName("form");

          form.addEventListener("submit", (ev) => {
            let formData = new FormData(form);
            let data = "";
            formData.forEach((value, key) => (data += key + "=" + value + "\\n"));
            console.log(data);
            alert("sending:\\n" + data);
            ev.preventDefault();
          });
        })();
      </script> `
}

Interaction tests (SwitchV3.interactions.stories.ts)

Interaction test stories (automated tests, not usage patterns).

Should Set Focus

Story: components-switch-interaction-tests--should-set-focus · tags: play-fn

<oc-switch-v3>
  <input type="checkbox" aria-label="oc-auto" checked />
  <label slot="label">Test Label</label>
</oc-switch-v3>
Story source (TypeScript, verbatim from Storybook)
{
  parameters: {
    docs: {
      disable: true
    }
  },
  render() {
    return html`
      <oc-switch-v3>
        <input type="checkbox" aria-label="oc-auto" checked />
        <label slot="label">Test Label</label>
      </oc-switch-v3>
    `;
  },
  async play({
    canvas
  }) {
    const input = (await canvas.findByRole("checkbox")) as HTMLInputElement;
    input.focus();
    await expect(input).toHaveFocus();
  }
}

Should Be On Or Off When Key Down

Story: components-switch-interaction-tests--should-be-on-or-off-when-key-down · tags: play-fn

<oc-switch-v3>
  <input type="checkbox" aria-label="oc-auto" />
  <label slot="label">Test Label</label>
</oc-switch-v3>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <oc-switch-v3>
        <input type="checkbox" aria-label="oc-auto" />
        <label slot="label">Test Label</label>
      </oc-switch-v3>
    `;
  },
  async play({
    canvas
  }) {
    const inputElement = (await canvas.findByRole("checkbox")) as HTMLInputElement;
    inputElement.focus();
    await expect(inputElement.checked).toBeFalsy();

    // On

    await click(inputElement);
    await expect(inputElement.checked).toBeTruthy();

    // Off

    await click(inputElement);
    await expect(inputElement.checked).toBeFalsy();
  }
}

Loading Flow

Story: components-switch-interaction-tests--loading-flow · tags: play-fn

<oc-switch-v3>
  <input type="checkbox" aria-label="oc-auto" />
  <label slot="label">Test Label</label>
</oc-switch-v3>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <oc-switch-v3>
        <input type="checkbox" aria-label="oc-auto" />
        <label slot="label">Test Label</label>
      </oc-switch-v3>
    `;
  },
  async play({
    canvas
  }) {
    const inputElement = (await canvas.findByRole("checkbox")) as HTMLInputElement;
    const container = inputElement.closest("oc-switch-v3") as HTMLOcSwitchV3Element;
    inputElement.focus();
    await expect(inputElement.checked).toBeFalsy();

    // On
    await click(inputElement);
    container.loading = true;
    await expect(inputElement.checked).toBeTruthy();
    await expect(container.loading).toBeTruthy();

    // wait for 100ms to simulate backend request
    await new Promise(res => {
      setTimeout(res, 100);
    });
    container.loading = false;
    await tick();

    // Off
    await click(inputElement);
    container.loading = true;
    await expect(inputElement.checked).toBeFalsy();
    await expect(container.loading).toBeTruthy();

    // wait for 100ms to simulate backend request
    await new Promise(res => {
      setTimeout(res, 100);
    });
    container.loading = false;
  }
}

Should Fire Change Event

Story: components-switch-interaction-tests--should-fire-change-event · tags: play-fn

<oc-switch-v3>
  <input type="checkbox" aria-label="oc-auto" />
  <label slot="label">Test Label</label>
</oc-switch-v3>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <oc-switch-v3>
        <input type="checkbox" aria-label="oc-auto" />
        <label slot="label">Test Label</label>
      </oc-switch-v3>
    `;
  },
  async play({
    canvas
  }) {
    const inputElement = (await canvas.findByRole("checkbox")) as HTMLInputElement;
    const switchComponent = inputElement.closest("oc-switch-v3") as HTMLOcSwitchV3Element;
    let changeEventFired = 0;
    switchComponent.addEventListener("change", () => {
      changeEventFired++;
    });

    // On

    inputElement.focus();
    await expect(inputElement.checked).toBeFalsy();
    await click(inputElement);
    await expect(changeEventFired).toBe(1);

    // Off

    inputElement.focus();
    await expect(inputElement.checked).toBeTruthy();
    await click(inputElement);
    await expect(changeEventFired).toBe(2);
  }
}

Should Fire Input Event

Story: components-switch-interaction-tests--should-fire-input-event · tags: play-fn

<oc-switch-v3>
  <input type="checkbox" aria-label="oc-auto" />
  <label slot="label">Test Label</label>
</oc-switch-v3>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <oc-switch-v3>
        <input type="checkbox" aria-label="oc-auto" />
        <label slot="label">Test Label</label>
      </oc-switch-v3>
    `;
  },
  async play({
    canvas
  }) {
    const inputElement = (await canvas.findByRole("checkbox")) as HTMLInputElement;
    const switchComponent = inputElement.closest("oc-switch-v3") as HTMLOcSwitchV3Element;
    let inputEventFired = 0;
    switchComponent.addEventListener("input", () => {
      inputEventFired++;
    });

    // On

    inputElement.checked = false;
    inputElement.focus();
    await expect(inputElement.checked).toBeFalsy();
    await click(inputElement);
    await expect(inputEventFired).toBe(1);

    // Off

    await expect(inputElement.checked).toBeTruthy();
    inputElement.focus();
    await click(inputElement);
    await expect(inputEventFired).toBe(2);
  }
}

Should Fire Click Event

Story: components-switch-interaction-tests--should-fire-click-event · tags: play-fn

<oc-switch-v3>
  <input type="checkbox" aria-label="oc-auto" />
  <label slot="label">Test Label</label>
</oc-switch-v3>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <oc-switch-v3>
        <input type="checkbox" aria-label="oc-auto" />
        <label slot="label">Test Label</label>
      </oc-switch-v3>
    `;
  },
  async play({
    canvas
  }) {
    const inputElement = (await canvas.findByRole("checkbox")) as HTMLInputElement;
    const switchComponent = inputElement.closest("oc-switch-v3") as HTMLOcSwitchV3Element;
    let clickEventFired = 0;
    switchComponent.addEventListener("click", () => {
      clickEventFired++;
    });

    // On

    inputElement.checked = false;
    inputElement.focus();
    await expect(inputElement.checked).toBeFalsy();
    await click(inputElement);
    await expect(clickEventFired).toBe(1);

    // Off

    await expect(inputElement.checked).toBeTruthy();
    inputElement.focus();
    await click(inputElement);
    await expect(clickEventFired).toBe(2);
  }
}

Should Prevent Click Event

Story: components-switch-interaction-tests--should-prevent-click-event · tags: play-fn

<oc-switch-v3>
  <input type="checkbox" aria-label="oc-auto" />
  <label slot="label">Test Label</label>
</oc-switch-v3>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <oc-switch-v3>
        <input type="checkbox" aria-label="oc-auto" />
        <label slot="label">Test Label</label>
      </oc-switch-v3>
    `;
  },
  async play({
    canvas
  }) {
    const inputElement = (await canvas.findByRole("checkbox")) as HTMLInputElement;
    const switchComponent = inputElement.closest("oc-switch-v3") as HTMLOcSwitchV3Element;
    switchComponent.addEventListener("click", ev => {
      ev.preventDefault();
    });

    // On

    inputElement.focus();
    await expect(inputElement.checked).toBeFalsy();
    await click(inputElement);
    await expect(inputElement.checked).toBeFalsy();

    // Off

    await expect(inputElement.checked).toBeFalsy();
    inputElement.focus();
    await click(inputElement);
    await expect(inputElement.checked).toBeFalsy();
  }
}

Should Have Pointer Events Set

Story: components-switch-interaction-tests--should-have-pointer-events-set · tags:

V2 (v2, deprecated, not for generation)

Source: ./src/components/switch/v2/Overview.mdx

Switch v2

Important

This is a deprecated version of the switch component. For the latest version, see the updated component documentation. Refer to this migration guide to update your project to the latest version.

The switch component provides a toggle functionality within a form. It has attributes for setting the initial checked state, a disabled state, the value to submit if checked, the name of the input element, the label placement, and the size of the switch. The switch can be initialized as checked or unchecked, and it has a loading state that can be toggled on or off.

Default variation

Story Default:

<oc-switch-v2 size="100" fit-content oc-aria-label="Switch purpose"></oc-switch-v2>

Configuration

The switch component offers the styling variants rectangular and circular. This component is configurable, allowing you to tailor its features and appearance to your specific needs. To explore all the available options and adjust the component, use the component configurator and see the changes affect the component in real-time.

Usage guidelines

Before integrating the switch component into your project, make sure you have correctly installed the OTTO components package. Look through the variations page for examples of possible component variations. Here, you can discover both common and specific variations that address different use cases.

Important

If the switch component is used in a form, pressing Enter while focused on the switch triggers the submit action immediately (implicit submit) if an oc-button component or a normal HTML button with the type submit is also present in the form.

Accessibility

The switch component comes with a set of built-in accessibility features to ensure a seamless experience for all users.

Use oc-aria-label

To make the switch recognizable for screen readers, use the default slot to provide a clear and descriptive label. Use the aria-label attribute on the label to provide additional context if the visible label is not sufficient. See the general accessibility documentation for guidance on using oc-aria-label, including how it works with link and masked link behavior.

aria-checked

The switch component automatically handles the aria-checked attribute. The aria-checked attribute indicates the current "checked" state of the switch component.

Keyboard navigation

The switch component supports keyboard navigation for accessibility purposes. The following table lists the keyboard shortcuts available for this component:

Shortcut Description
Tab Focuses the switch element
Space / Enter Toggles the switch element on or off
Further reading

V2/Configuration (v2, deprecated, not for generation)

Source: ./src/components/switch/v2/Configuration.mdx

Switch V2 configuration

Configure the switch component with the controls below and see the changes live in the preview canvas. Click the Show code button within the preview canvas to see the source code for the current component configuration.

Story Default:

<oc-switch-v2 size="100" fit-content oc-aria-label="Switch purpose"></oc-switch-v2>

Interactive configurator (Storybook controls); every option is listed in the API section of this file.

V2/Interaction tests (SwitchV2.interactions.stories.ts)

Interaction test stories (automated tests, not usage patterns).

V 2 Should Set Focus

Story: components-switch-v2-interaction-tests--v-2-should-set-focus · tags: play-fn

<oc-switch-v2 checked>Test Label</oc-switch-v2>
Story source (TypeScript, verbatim from Storybook)
{
  parameters: {
    docs: {
      disable: true
    }
  },
  render() {
    return html` <oc-switch-v2 checked>Test Label</oc-switch-v2> `;
  },
  async play({
    canvasElement
  }) {
    const container = Array.from(canvasElement.getElementsByTagName("oc-switch-v2"))[0];
    container.focus();
    await expect(container).toHaveFocus();
  }
}

V 2 Should Be On Or Off When Key Down

Story: components-switch-v2-interaction-tests--v-2-should-be-on-or-off-when-key-down · tags: play-fn

<oc-switch-v2>Test Label</oc-switch-v2>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html` <oc-switch-v2>Test Label</oc-switch-v2> `;
  },
  async play({
    canvasElement
  }) {
    const container = Array.from(canvasElement.getElementsByTagName("oc-switch-v2"))[0];
    const inputElement = deepQuerySelector(container, ".input")!;
    container.focus();
    await expect(container.checked).toBeFalsy();

    // On

    await click(inputElement);
    await expect(inputElement).toHaveAttribute("aria-checked", "true");
    await expect(container.checked).toBeTruthy();

    // Off

    await click(inputElement);
    await expect(inputElement).toHaveAttribute("aria-checked", "false");
    await expect(container.checked).toBeFalsy();
  }
}

V 2 Loading Flow

Story: components-switch-v2-interaction-tests--v-2-loading-flow · tags: play-fn

<oc-switch-v2>Test Label</oc-switch-v2>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html` <oc-switch-v2>Test Label</oc-switch-v2> `;
  },
  async play({
    canvasElement
  }) {
    const container: HTMLOcSwitchV2Element & {
      isLoading?: boolean;
    } = Array.from(canvasElement.getElementsByTagName("oc-switch-v2"))[0];
    const inputElement = deepQuerySelector(container, ".input")!;
    container.focus();
    await expect(container.checked).toBeFalsy();

    // On
    await click(inputElement);
    container.loading = true;
    await expect(inputElement).toHaveAttribute("aria-checked", "true");
    await expect(container.checked).toBeTruthy();
    await expect(container.loading).toBeTruthy();

    // wait for three seconds to simulate backend request
    await new Promise(res => {
      setTimeout(res, 100);
    });
    container.loading = false;
    await tick();

    // wait for one second to perform the next action

    // Off
    await click(inputElement);
    container.loading = true;
    await expect(inputElement).toHaveAttribute("aria-checked", "false");
    await expect(container.checked).toBeFalsy();
    await expect(container.loading).toBeTruthy();

    // wait for three seconds to simulate backend request
    await new Promise(res => {
      setTimeout(res, 100);
    });
    container.loading = false;
  }
}

V 2 Should Fire Change Event

Story: components-switch-v2-interaction-tests--v-2-should-fire-change-event · tags: play-fn

<oc-switch-v2>Test Label</oc-switch-v2>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html` <oc-switch-v2>Test Label</oc-switch-v2> `;
  },
  async play({
    canvasElement
  }) {
    const switchComponent: HTMLOcSwitchV2Element = Array.from(canvasElement.getElementsByTagName("oc-switch-v2"))[0]!;
    const inputElement: HTMLInputElement | null = deepQuerySelector(switchComponent, ".input")!;
    let changeEventFired = false;
    switchComponent.addEventListener("change", () => {
      changeEventFired = true;
    });

    // On

    inputElement.focus();
    await expect(inputElement.checked).toBeFalsy();
    await click(inputElement);
    await expect(changeEventFired).toBeTruthy();

    // Off

    inputElement!.focus();
    await expect(inputElement.checked).toBeTruthy();
    await click(inputElement);
    await expect(changeEventFired).toBeTruthy();
  }
}

V 2 Should Fire Input Event

Story: components-switch-v2-interaction-tests--v-2-should-fire-input-event · tags: play-fn

<oc-switch-v2>Test Label</oc-switch-v2>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html` <oc-switch-v2>Test Label</oc-switch-v2> `;
  },
  async play({
    canvasElement
  }) {
    const switchComponent: HTMLOcSwitchV2Element = Array.from(canvasElement.getElementsByTagName("oc-switch-v2"))[0]!;
    const inputElement: HTMLInputElement | null = deepQuerySelector(switchComponent, ".input")!;
    let inputEventFired = false;
    switchComponent.addEventListener("input", () => {
      inputEventFired = true;
    });

    // On

    inputElement.checked = false;
    inputElement.focus();
    await expect(inputElement.checked).toBeFalsy();
    await click(inputElement);
    await expect(inputEventFired).toBeTruthy();

    // Off

    await expect(inputElement.checked).toBeTruthy();
    inputElement.focus();
    await click(inputElement);
    await expect(inputEventFired).toBeTruthy();
  }
}

V 2 Should Fire Click Event

Story: components-switch-v2-interaction-tests--v-2-should-fire-click-event · tags: play-fn

<oc-switch-v2>Test Label</oc-switch-v2>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html` <oc-switch-v2>Test Label</oc-switch-v2> `;
  },
  async play({
    canvasElement
  }) {
    const switchComponent: HTMLOcSwitchV2Element = Array.from(canvasElement.getElementsByTagName("oc-switch-v2"))[0]!;
    const inputElement: HTMLInputElement | null = deepQuerySelector(switchComponent, ".input")!;
    let clickEventFired = false;
    switchComponent.addEventListener("click", () => {
      clickEventFired = true;
    });

    // On

    inputElement.checked = false;
    inputElement.focus();
    await expect(inputElement.checked).toBeFalsy();
    await click(inputElement);
    await expect(clickEventFired).toBeTruthy();

    // Off

    await expect(inputElement.checked).toBeTruthy();
    inputElement.focus();
    await click(inputElement);
    await expect(clickEventFired).toBeTruthy();
  }
}

V 2 Should Prevent Click Event

Story: components-switch-v2-interaction-tests--v-2-should-prevent-click-event · tags: play-fn

<oc-switch-v2>Test Label</oc-switch-v2>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html` <oc-switch-v2>Test Label</oc-switch-v2> `;
  },
  async play({
    canvasElement
  }) {
    const switchComponent: HTMLOcSwitchV2Element = Array.from(canvasElement.getElementsByTagName("oc-switch-v2"))[0]!;
    const inputElement: HTMLInputElement | null = deepQuerySelector(switchComponent, ".input")!;
    switchComponent.addEventListener("click", ev => {
      ev.preventDefault();
    });

    // On

    inputElement.focus();
    await expect(inputElement.checked).toBeFalsy();
    await click(inputElement);
    await expect(switchComponent.checked).toBeFalsy();

    // Off

    await expect(inputElement.checked).toBeFalsy();
    inputElement.focus();
    await click(inputElement);
    await expect(switchComponent.checked).toBeFalsy();
  }
}

V 2 Handle Default Value And Default Checked

Story: components-switch-v2-interaction-tests--v-2-handle-default-value-and-default-checked · tags: play-fn

<oc-switch-v2>no defaults set</oc-switch-v2>
<oc-switch-v2 value="test" checked>Test Label</oc-switch-v2>
Story source (TypeScript, verbatim from Storybook)
{
  parameters: {
    chromatic: {
      disableSnapshot: true
    }
  },
  render() {
    return html`
      <oc-switch-v2>no defaults set</oc-switch-v2>
      <oc-switch-v2 value="test" checked>Test Label</oc-switch-v2>
    `;
  },
  async play({
    canvasElement
  }) {
    const [sutWithoutAttributes, sutWithAttributes] = Array.from(canvasElement.getElementsByTagName("oc-switch-v2")) as (HTMLOcSwitchV2Element & {
      defaultChecked: boolean;
      defaultValue: string;
    })[];

    // TEST sutWithoutAttributes
    await expect(sutWithoutAttributes.checked).toBeFalsy();
    await expect(sutWithoutAttributes.defaultChecked).toBeFalsy();
    await expect(sutWithoutAttributes.hasAttribute("checked")).toBeFalsy();
    await expect(sutWithoutAttributes.value).toBe("on");
    await expect(sutWithoutAttributes.defaultValue).toBe("");
    await expect(sutWithoutAttributes.hasAttribute("value")).toBeFalsy();
    sutWithoutAttributes.checked = true;
    sutWithoutAttributes.value = "new on";
    await expect(sutWithoutAttributes.checked).toBeTruthy();
    await expect(sutWithoutAttributes.defaultChecked).toBeFalsy();
    await expect(sutWithoutAttributes.hasAttribute("checked")).toBeFalsy();
    await expect(sutWithoutAttributes.value).toBe("new on");
    // await expect(sutWithoutAttributes.defaultValue).toBe("new on");
    // await expect(sutWithoutAttributes.getAttribute("value")).toBe("new on");

    // TEST sutWithAttributes
    await expect(sutWithAttributes.checked).toBeTruthy();
    await expect(sutWithAttributes.defaultChecked).toBeTruthy();
    await expect(sutWithAttributes.hasAttribute("checked")).toBeTruthy();
    await expect(sutWithAttributes.value).toBe("test");
    await expect(sutWithAttributes.defaultValue).toBe("test");
    await expect(sutWithAttributes.getAttribute("value")).toBe("test");
    sutWithAttributes.checked = false;
    sutWithAttributes.value = "new test";
    await expect(sutWithAttributes.checked).toBeFalsy();
    await expect(sutWithAttributes.defaultChecked).toBeTruthy();
    await expect(sutWithAttributes.hasAttribute("checked")).toBeTruthy();
    await expect(sutWithAttributes.value).toBe("new test");
    // await expect(sutWithAttributes.defaultValue).toBe("new test");
    // await expect(sutWithAttributes.getAttribute("value")).toBe("new test");

    // TEST programmatic created
    const sutCreated = document.createElement("oc-switch-v2") as HTMLOcSwitchV2Element & {
      defaultChecked: boolean;
      defaultValue: string;
    };
    sutCreated.innerText = "created";
    sutCreated.checked = true;
    canvasElement.append(sutCreated);
    await expect(sutCreated.checked).toBeTruthy();
    await expect(sutCreated.defaultChecked).toBeFalsy();
    await expect(sutCreated.hasAttribute("checked")).toBeFalsy();
    await expect(sutCreated.value).toBe("on");
    await expect(sutCreated.defaultValue).toBe("");
    await expect(sutCreated.hasAttribute("value")).toBeFalsy();
    sutCreated.defaultChecked = true;
    sutCreated.defaultValue = "foo value";
    await expect(sutCreated.checked).toBeTruthy();
    await expect(sutCreated.defaultChecked).toBeTruthy();
    await expect(sutCreated.hasAttribute("checked")).toBeTruthy();
    await expect(sutCreated.value).toBe("foo value");
    await expect(sutCreated.defaultValue).toBe("foo value");
    await expect(sutCreated.getAttribute("value")).toBe("foo value");
  }
}

V2/Variations (v2, deprecated, not for generation)

Source: ./src/components/switch/v2/Variations.mdx

Variations

Listed below are the most common variations of the switch component as well as specific component variations for different use cases.

You can explore all available options using the component configurator, adjust the component to your needs, and see the changes live in a preview canvas.

Default

Story: components-switch-v2-variations--default · tags: components

The default configuration has size=100 and has no label. Instead of a label, it uses the oc-aria-label attribute to describe the purpose of the switch.

Args: oc-aria-label=Switch purpose

<oc-switch-v2 size="100" fit-content oc-aria-label="Switch purpose"></oc-switch-v2>

With label

Story: components-switch-v2-variations--with-label · tags: components

Variation of the switch with a visible label passed via the default slot and using the label-placement attribute to place the label to the right of the switch.

Args: defaultSlot=Switch label, label-placement=right

<oc-switch-v2 size="100" fit-content label-placement="right">Switch label</oc-switch-v2>

Disabled

Story: components-switch-v2-variations--disabled · tags: components

Variation with the disabled attribute set to true, preventing user interaction.

Args: defaultSlot=Switch label, disabled=true

<oc-switch-v2 size="100" disabled fit-content>Switch label</oc-switch-v2>

Demo: loading state

Story: components-switch-v2-variations--demo-loading-state · tags: components

<oc-switch-v2 label-placement="left">Click me</oc-switch-v2>

<script>
  (() => {
    const [switchElement] = document.getElementsByTagName("oc-switch-v2");
    switchElement.addEventListener("click", () => {
      if (switchElement.loading) {
        return;
      }

      switchElement.loading = true;
      setTimeout(() => {
        switchElement.loading = false;
      }, 1000);
    });
  })();
</script>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: loading state",
  argTypes: hideControlsBadge(Metadata),
  parameters: {
    controls: {
      disabled: true
    },
    // Disables Chromatic's snapshotting, as the spinner is animated und thus leads to diffs
    chromatic: {
      disableSnapshot: true
    }
  },
  render: () => {
    return html` <oc-switch-v2 label-placement="left">Click me</oc-switch-v2>

      <script>
        (() => {
          const [switchElement] = document.getElementsByTagName("oc-switch-v2");
          switchElement.addEventListener("click", () => {
            if (switchElement.loading) {
              return;
            }

            switchElement.loading = true;
            setTimeout(() => {
              switchElement.loading = false;
            }, 1000);
          });
        })();
      </script>`;
  }
}

Demo: form with switch

Story: components-switch-v2-variations--demo-form · tags: components

A demo form showcasing the switch that logs the form data to the console and shows an alert with the data.

<div>
  <form>
    <oc-switch-v2 name="test" label-placement="left">Switch label</oc-switch-v2>
    <br />
    <oc-switch-v2 disabled label-placement="left">Disabled switch</oc-switch-v2>
    <br />
    <oc-button-v1 type="submit">Submit</oc-button-v1>
  </form>
</div>
<script>
  (() => {
    const [form] = document.getElementsByTagName("form");

    form.addEventListener("submit", (ev) => {
      let formData = new FormData(form);
      let data = "";
      formData.forEach((value, key) => (data += key + "=" + value + "\\n"));
      console.log(data);
      alert("sending:\\n" + data);
      ev.preventDefault();
    });
  })();
</script>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: form with switch",
  argTypes: hideControlsBadge(Metadata),
  parameters: {
    controls: {
      disabled: true
    }
  },
  render: () => html`<div>
        <form>
          <oc-switch-v2 name="test" label-placement="left">Switch label</oc-switch-v2>
          <br />
          <oc-switch-v2 disabled label-placement="left">Disabled switch</oc-switch-v2>
          <br />
          <oc-button-v1 type="submit">Submit</oc-button-v1>
        </form>
      </div>
      <script>
        (() => {
          const [form] = document.getElementsByTagName("form");

          form.addEventListener("submit", (ev) => {
            let formData = new FormData(form);
            let data = "";
            formData.forEach((value, key) => (data += key + "=" + value + "\\n"));
            console.log(data);
            alert("sending:\\n" + data);
            ev.preventDefault();
          });
        })();
      </script> `
}

Demo: exposed state

Story: components-switch-v2-variations--demo-exposed-state · tags: components

<oc-switch-v2 checked label-placement="left">switch</oc-switch-v2>
<p>
  the background is red, when the modern syntax<br /><code
    >oc-switch-v2:state(checked) {}</code
  >
  applies
</p>
<p>
  for demo purpose, it is yellow in older browsers
  <br /><code>oc-switch-v2[state--checked] {}</code>
</p>

<style>
  /* Unknown pseudo selector 'state'?, You are wrong IntelliJ, that is valid
   https://developer.mozilla.org/en-US/docs/Web/CSS/:state
   */
  oc-switch-v2:state(checked) {
    background-color: #f00;
  }
  /* in older browsers, this is done by a mixin packages/otto-components-utils/scss/_mixins/custom-state.scss */
  oc-switch-v2[state--checked] {
    background: yellow;
  }
</style>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Demo: exposed state",
  argTypes: hideControlsBadge(Metadata),
  parameters: {
    controls: {
      disabled: true
    },
    // Disables Chromatic's snapshotting, as the spinner is animated und thus leads to diffs
    chromatic: {
      disableSnapshot: true
    }
  },
  render: () => {
    return html` <oc-switch-v2 checked label-placement="left">switch</oc-switch-v2>
      <p>
        the background is red, when the modern syntax<br /><code
          >oc-switch-v2:state(checked) {}</code
        >
        applies
      </p>
      <p>
        for demo purpose, it is yellow in older browsers
        <br /><code>oc-switch-v2[state--checked] {}</code>
      </p>

      <style>
        /* Unknown pseudo selector 'state'?, You are wrong IntelliJ, that is valid
         https://developer.mozilla.org/en-US/docs/Web/CSS/:state
         */
        oc-switch-v2:state(checked) {
          background-color: #f00;
        }
        /* in older browsers, this is done by a mixin packages/otto-components-utils/scss/_mixins/custom-state.scss */
        oc-switch-v2[state--checked] {
          background: yellow;
        }
      </style>`;
  }
}