| 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:
clickinputchangefocusblur
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-selectionFor 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
Enterwhile focused on the switch triggers the submit action immediately (implicit submit) if anoc-buttoncomponent or a normal HTML button with the typesubmitis 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>`;
}
}