+
+
+ Send anonymous usage data — read our privacy policy
+
+
+
+
+
+
diff --git a/dev/toggle-switch.html b/dev/toggle-switch.html
new file mode 100644
index 00000000000..20fb0c5ec57
--- /dev/null
+++ b/dev/toggle-switch.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Toggle Switch
+
+
+
+
+
+
+
+
diff --git a/packages/aura/aura.css b/packages/aura/aura.css
index 5c3dcf07a55..513798f0bba 100644
--- a/packages/aura/aura.css
+++ b/packages/aura/aura.css
@@ -39,6 +39,7 @@
@import './src/components/side-nav.css';
@import './src/components/slider.css';
@import './src/components/tabs.css';
+@import './src/components/toggle-switch.css';
@import './src/components/tooltip.css';
@import './src/components/upload.css';
diff --git a/packages/aura/src/components/toggle-switch.css b/packages/aura/src/components/toggle-switch.css
new file mode 100644
index 00000000000..1f0695fb370
--- /dev/null
+++ b/packages/aura/src/components/toggle-switch.css
@@ -0,0 +1,71 @@
+:where(:root),
+:where(:host) {
+ --vaadin-toggle-switch-size: round(1lh - 2px, 2px);
+}
+
+vaadin-toggle-switch::part(switch) {
+ transition:
+ background-color 100ms,
+ box-shadow 100ms;
+}
+
+vaadin-toggle-switch:not([disabled], [readonly])::part(switch) {
+ --aura-surface-level: 4;
+ background: var(--vaadin-toggle-switch-background, var(--aura-surface-color));
+ box-shadow: var(--aura-shadow-xs);
+ --_shade: color-mix(in srgb, var(--vaadin-border-color-secondary) 50%, transparent);
+ background-image: linear-gradient(
+ light-dark(transparent, var(--_shade)),
+ transparent 33%,
+ transparent 66%,
+ light-dark(var(--_shade), transparent)
+ );
+}
+
+vaadin-toggle-switch:not([checked])::part(switch) {
+ background-clip: padding-box;
+}
+
+vaadin-toggle-switch[checked]:not([readonly], [disabled])::part(switch) {
+ --vaadin-toggle-switch-background: var(--aura-accent-color);
+ --vaadin-toggle-switch-border-color: var(--vaadin-border-color-secondary);
+ --vaadin-toggle-switch-thumb-checked-color: var(--aura-accent-contrast-color);
+ background-image: none;
+ box-shadow: var(--aura-shadow-s);
+}
+
+vaadin-toggle-switch[invalid]:not([disabled], [readonly])::part(switch) {
+ --vaadin-toggle-switch-background: color-mix(in srgb, var(--aura-red) 10%, transparent);
+ --vaadin-toggle-switch-border-color: var(--aura-red-text);
+ background-image: none;
+}
+
+vaadin-toggle-switch[invalid][checked]:not([disabled], [readonly])::part(switch) {
+ --vaadin-toggle-switch-background: var(--aura-red);
+ --vaadin-toggle-switch-border-color: var(--vaadin-border-color-secondary);
+ --vaadin-toggle-switch-thumb-checked-color: var(--aura-accent-contrast-color);
+}
+
+vaadin-toggle-switch:not([disabled], [readonly])::part(switch)::before {
+ content: '';
+ position: absolute;
+ inset: calc(var(--vaadin-toggle-switch-border-width, var(--vaadin-input-field-border-width, 1px)) * -1);
+ border-radius: inherit;
+ background-color: currentColor;
+ opacity: 0;
+ transition:
+ opacity 100ms,
+ background-color 100ms;
+ pointer-events: none;
+}
+
+@media (any-hover: hover) {
+ vaadin-toggle-switch:hover:not([readonly], [disabled], [active])::part(switch)::before {
+ opacity: 0.04;
+ }
+}
+
+vaadin-toggle-switch[active]:not([readonly], [disabled])::part(switch)::before {
+ opacity: 0.1;
+ background: #000;
+}
diff --git a/packages/checkbox/src/vaadin-checkbox-mixin.d.ts b/packages/checkbox/src/vaadin-checkbox-mixin.d.ts
index 1e89f70de05..e8c8b5f0acb 100644
--- a/packages/checkbox/src/vaadin-checkbox-mixin.d.ts
+++ b/packages/checkbox/src/vaadin-checkbox-mixin.d.ts
@@ -38,15 +38,6 @@ export declare function CheckboxMixin>(
T;
export declare class CheckboxMixinClass {
- /**
- * True if the checkbox is in the indeterminate state which means
- * it is not possible to say whether it is checked or unchecked.
- * The state is reset once the user switches the checkbox by hand.
- *
- * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#Indeterminate_state_checkboxes
- */
- indeterminate: boolean;
-
/**
* The name of the checkbox.
*/
diff --git a/packages/checkbox/src/vaadin-checkbox-mixin.js b/packages/checkbox/src/vaadin-checkbox-mixin.js
index b3ff5ef4d83..9501258361e 100644
--- a/packages/checkbox/src/vaadin-checkbox-mixin.js
+++ b/packages/checkbox/src/vaadin-checkbox-mixin.js
@@ -20,20 +20,6 @@ export const CheckboxMixin = (superclass) =>
) {
static get properties() {
return {
- /**
- * True if the checkbox is in the indeterminate state which means
- * it is not possible to say whether it is checked or unchecked.
- * The state is reset once the user switches the checkbox by hand.
- *
- * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#Indeterminate_state_checkboxes
- */
- indeterminate: {
- type: Boolean,
- notify: true,
- value: false,
- reflectToAttribute: true,
- },
-
/**
* The name of the checkbox.
*/
@@ -60,11 +46,6 @@ export const CheckboxMixin = (superclass) =>
return ['__readonlyChanged(readonly, inputElement)'];
}
- /** @override */
- static get delegateProps() {
- return [...super.delegateProps, 'indeterminate'];
- }
-
/** @override */
static get delegateAttrs() {
return [...super.delegateAttrs, 'name', 'invalid', 'required'];
@@ -187,22 +168,6 @@ export const CheckboxMixin = (superclass) =>
}
}
- /**
- * Override method inherited from `CheckedMixin` to reset
- * `indeterminate` state checkbox is toggled by the user.
- *
- * @param {boolean} checked
- * @protected
- * @override
- */
- _toggleChecked(checked) {
- if (this.indeterminate) {
- this.indeterminate = false;
- }
-
- super._toggleChecked(checked);
- }
-
/**
* @override
* @return {boolean}
diff --git a/packages/checkbox/src/vaadin-checkbox.d.ts b/packages/checkbox/src/vaadin-checkbox.d.ts
index 1580f095660..c4a5862e441 100644
--- a/packages/checkbox/src/vaadin-checkbox.d.ts
+++ b/packages/checkbox/src/vaadin-checkbox.d.ts
@@ -120,6 +120,15 @@ export interface CheckboxEventMap extends HTMLElementEventMap, CheckboxCustomEve
* @fires {CustomEvent} validated - Fired whenever the field is validated.
*/
declare class Checkbox extends CheckboxMixin(ElementMixin(ThemableMixin(HTMLElement))) {
+ /**
+ * True if the checkbox is in the indeterminate state which means
+ * it is not possible to say whether it is checked or unchecked.
+ * The state is reset once the user switches the checkbox by hand.
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#Indeterminate_state_checkboxes
+ */
+ indeterminate: boolean;
+
addEventListener(
type: K,
listener: (this: Checkbox, ev: CheckboxEventMap[K]) => void,
diff --git a/packages/checkbox/src/vaadin-checkbox.js b/packages/checkbox/src/vaadin-checkbox.js
index 31dcc847705..c7b354a2169 100644
--- a/packages/checkbox/src/vaadin-checkbox.js
+++ b/packages/checkbox/src/vaadin-checkbox.js
@@ -96,6 +96,30 @@ export class Checkbox extends CheckboxMixin(ElementMixin(ThemableMixin(PolylitMi
return checkboxStyles;
}
+ static get properties() {
+ return {
+ /**
+ * True if the checkbox is in the indeterminate state which means
+ * it is not possible to say whether it is checked or unchecked.
+ * The state is reset once the user switches the checkbox by hand.
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#Indeterminate_state_checkboxes
+ *
+ */
+ indeterminate: {
+ type: Boolean,
+ notify: true,
+ value: false,
+ reflectToAttribute: true,
+ },
+ };
+ }
+
+ /** @override */
+ static get delegateProps() {
+ return [...super.delegateProps, 'indeterminate'];
+ }
+
/** @protected */
render() {
return html`
@@ -125,6 +149,22 @@ export class Checkbox extends CheckboxMixin(ElementMixin(ThemableMixin(PolylitMi
this._tooltipController.setAriaTarget(this.inputElement);
this.addController(this._tooltipController);
}
+
+ /**
+ * Override method inherited from `CheckedMixin` to reset
+ * `indeterminate` when the checkbox is toggled by the user.
+ *
+ * @param {boolean} checked
+ * @protected
+ * @override
+ */
+ _toggleChecked(checked) {
+ if (this.indeterminate) {
+ this.indeterminate = false;
+ }
+
+ super._toggleChecked(checked);
+ }
}
defineCustomElement(Checkbox);
diff --git a/packages/toggle-switch/LICENSE b/packages/toggle-switch/LICENSE
new file mode 100644
index 00000000000..c8403c8530f
--- /dev/null
+++ b/packages/toggle-switch/LICENSE
@@ -0,0 +1,190 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2025-2026 Vaadin Ltd.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/toggle-switch/README.md b/packages/toggle-switch/README.md
new file mode 100644
index 00000000000..fe165082158
--- /dev/null
+++ b/packages/toggle-switch/README.md
@@ -0,0 +1,36 @@
+# @vaadin/toggle-switch
+
+A web component that displays a binary on/off switch control.
+
+> ⚠️ This component is experimental and the API may change. In order to use it, enable the feature flag by setting `window.Vaadin.featureFlags.toggleSwitchComponent = true`.
+
+```html
+
+```
+
+[Documentation + Live Demo ↗](https://vaadin.com/docs/latest/components/toggle-switch)
+
+## Installation
+
+Install the component:
+
+```sh
+npm i @vaadin/toggle-switch
+```
+
+Once installed, import the component in your application:
+
+```js
+import '@vaadin/toggle-switch';
+```
+
+## Contributing
+
+Read the [contributing guide](https://vaadin.com/docs/latest/contributing) to learn about our development process, how to propose bugfixes and improvements, and how to test your changes to Vaadin components.
+
+## License
+
+Apache License 2.0
+
+Vaadin collects usage statistics at development time to improve this product.
+For details and to opt-out, see https://github.com/vaadin/vaadin-usage-statistics.
diff --git a/packages/toggle-switch/package.json b/packages/toggle-switch/package.json
new file mode 100644
index 00000000000..561a6866129
--- /dev/null
+++ b/packages/toggle-switch/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "@vaadin/toggle-switch",
+ "version": "25.2.0-alpha12",
+ "publishConfig": {
+ "access": "public"
+ },
+ "description": "Web component that displays a binary on/off switch control.",
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/vaadin/web-components.git",
+ "directory": "packages/toggle-switch"
+ },
+ "author": "Vaadin Ltd",
+ "homepage": "https://vaadin.com/components",
+ "bugs": {
+ "url": "https://github.com/vaadin/web-components/issues"
+ },
+ "main": "vaadin-toggle-switch.js",
+ "module": "vaadin-toggle-switch.js",
+ "type": "module",
+ "files": [
+ "src",
+ "vaadin-*.d.ts",
+ "vaadin-*.js",
+ "custom-elements.json",
+ "web-types.json",
+ "web-types.lit.json"
+ ],
+ "keywords": [
+ "Vaadin",
+ "toggle-switch",
+ "switch",
+ "web-components",
+ "web-component"
+ ],
+ "dependencies": {
+ "@open-wc/dedupe-mixin": "^1.3.0",
+ "@vaadin/checkbox": "25.2.0-alpha12",
+ "@vaadin/component-base": "25.2.0-alpha12",
+ "@vaadin/field-base": "25.2.0-alpha12",
+ "@vaadin/vaadin-themable-mixin": "25.2.0-alpha12",
+ "lit": "^3.0.0"
+ },
+ "devDependencies": {
+ "@vaadin/aura": "25.2.0-alpha12",
+ "@vaadin/chai-plugins": "25.2.0-alpha12",
+ "@vaadin/test-runner-commands": "25.2.0-alpha12",
+ "@vaadin/testing-helpers": "^2.0.0",
+ "@vaadin/vaadin-lumo-styles": "25.2.0-alpha12",
+ "sinon": "^21.0.2"
+ },
+ "customElements": "custom-elements.json",
+ "web-types": [
+ "web-types.json",
+ "web-types.lit.json"
+ ]
+}
diff --git a/packages/toggle-switch/spec/flow-api.md b/packages/toggle-switch/spec/flow-api.md
new file mode 100644
index 00000000000..1e7256acf35
--- /dev/null
+++ b/packages/toggle-switch/spec/flow-api.md
@@ -0,0 +1,324 @@
+# Toggle Switch Flow Developer API
+
+Java wrapper for ``. Class: `ToggleSwitch` in package `com.vaadin.flow.component.toggleswitch`.
+
+The Flow class follows the `Checkbox` pattern from `vaadin-checkbox-flow` line-for-line: same base class, same shared mixins (`InputField`, `HasValidationProperties`, `HasAriaLabel`, `HasValidator`), same `ClickNotifier` / `Focusable`, same nested `ToggleSwitchI18n` for the required-error message. The intentional differences are (1) no `indeterminate`-related API — toggle switches do not have an indeterminate state per the problem statement — and (2) no `HasThemeVariant` / typed `ToggleSwitchVariant` enum, see Discussion.
+
+---
+
+## 1. Basic instantiation, default-off state, on/off value, ARIA switch role
+
+Covers requirement(s): 1, 4
+
+```java
+// Default: starts off
+ToggleSwitch notifications = new ToggleSwitch("Notifications");
+add(notifications);
+
+// Initial state: on
+ToggleSwitch darkMode = new ToggleSwitch("Dark mode", true);
+add(darkMode);
+
+// Read / write the current state programmatically
+boolean isOn = notifications.getValue();
+notifications.setValue(true);
+
+// No-arg constructor for late-set label
+ToggleSwitch s = new ToggleSwitch();
+s.setLabel("Auto-save");
+add(s);
+```
+
+**Why this shape:** Mirrors `Checkbox` from `vaadin-checkbox-flow`. The component extends `AbstractSinglePropertyField` so the on/off state is the field's `Boolean` value — making it a drop-in `HasValue` for `Binder` and other data-binding code. The `(String label)`, `(boolean initialValue)`, and `(String, boolean)` convenience constructors match the Checkbox set; no separate constructor is needed for "switch role" because the role is exposed automatically by the underlying web component, with no Flow-level switch.
+
+---
+
+## 2. Value change listener
+
+Covers requirement(s): 2
+
+```java
+ToggleSwitch dailyDigest = new ToggleSwitch("Daily digest");
+
+dailyDigest.addValueChangeListener(event -> {
+ // event.isFromClient() distinguishes user vs. programmatic updates
+ if (event.isFromClient()) {
+ userPreferences.setDailyDigest(event.getValue());
+ }
+});
+
+// Convenience constructor: label + listener in one go
+ToggleSwitch compare = new ToggleSwitch("Compare with previous period",
+ event -> dashboard.toggleOverlay(event.getValue()));
+add(compare);
+```
+
+**Why this shape:** The web component's `change` event (user-initiated) and `checked-changed` event (any change) collapse into a single Flow `ValueChangeListener` whose event carries `isFromClient()` for the user-vs-programmatic distinction — the standard Vaadin Flow way of handling this split. Matches `Checkbox.addValueChangeListener` exactly. The two-arg `(String, ValueChangeListener)` constructor mirrors the corresponding Checkbox constructor for the most common call site.
+
+---
+
+## 3. Label, label component, accessible name
+
+Covers requirement(s): 3
+
+```java
+// Plain-text label (most common)
+ToggleSwitch s = new ToggleSwitch("Email me when I'm @mentioned");
+
+// Replace the label with a custom component when HTML / inline children are needed
+ToggleSwitch consent = new ToggleSwitch();
+HorizontalLayout label = new HorizontalLayout(
+ new Span("Send anonymous usage data — "),
+ new Anchor("/privacy", "read our privacy policy"));
+label.setSpacing(false);
+consent.setLabelComponent(label);
+add(consent);
+
+// Label-less switch (e.g. inside a Grid column where the column header is the name)
+ToggleSwitch active = new ToggleSwitch();
+active.setAriaLabel("Active");
+// Or reference an external label by id
+active.setAriaLabelledBy("row-3-active-label");
+```
+
+**Why this shape:** `setLabel(String)` comes from `HasLabel` (transitive via `InputField`), `setLabelComponent(Component)` matches the Checkbox additional method for HTML-in-label cases, and `setAriaLabel(String)` / `setAriaLabelledBy(String)` come from `HasAriaLabel` (Flow core). All three mirror the public surface of `Checkbox`. The component refuses to flip when the user clicks an interactive child of a label component; this is web-component behavior, no Flow API surface.
+
+---
+
+## 4. Disabled
+
+Covers requirement(s): 5
+
+```java
+ToggleSwitch dailyDigest = new ToggleSwitch("Daily digest");
+dailyDigest.setEnabled(false); // not focusable, not Tab-reachable, no interaction
+
+// Programmatic flips are still allowed and silent (no value-change event from user)
+ToggleSwitch parent = new ToggleSwitch("Email me on activity");
+ToggleSwitch child = new ToggleSwitch("Daily digest");
+parent.addValueChangeListener(e -> {
+ child.setEnabled(e.getValue());
+ if (!e.getValue()) {
+ child.setValue(false); // programmatic update — listener will see isFromClient=false
+ }
+});
+```
+
+**Why this shape:** `setEnabled(boolean)` is the Vaadin Flow convention (`HasEnabled` via `InputField`); a separate `setDisabled` would be an idiosyncratic deviation. Matches Checkbox.
+
+---
+
+## 5. Read-only
+
+Covers requirement(s): 6
+
+```java
+// Read-only switch reflecting a plan-locked setting
+ToggleSwitch retention = new ToggleSwitch("Audit log retention (90 days)", true);
+retention.setHelperText("Included on the Business plan.");
+retention.setReadOnly(true);
+add(retention);
+
+// Read-only + required: validation rule "valid if on, invalid if off" still applies
+ToggleSwitch verified = new ToggleSwitch("Account verified", true);
+verified.setReadOnly(true);
+verified.setRequiredIndicatorVisible(true);
+```
+
+**Why this shape:** `setReadOnly(boolean)` is part of `HasValue` (transitive via `InputField`), so applications already know it from every other Vaadin field. The web component's `aria-readonly` qualifier and the read-only-still-submits-with-form distinction are handled internally by the web component; no Flow-level API.
+
+---
+
+## 6. Required validation, error message, manual validation, i18n
+
+Covers requirement(s): 7, 9
+
+```java
+// Built-in required validation: turning the switch on satisfies it; off is invalid
+ToggleSwitch terms = new ToggleSwitch("I confirm the trip details are correct");
+terms.setRequiredIndicatorVisible(true);
+terms.setErrorMessage("You must accept the trip details to continue");
+
+// i18n-supplied default required message (e.g. localized at app level)
+ToggleSwitch terms2 = new ToggleSwitch("I accept the booking terms");
+terms2.setRequiredIndicatorVisible(true);
+terms2.setI18n(new ToggleSwitch.ToggleSwitchI18n()
+ .setRequiredErrorMessage(getTranslation("toggleSwitch.required")));
+
+// Manual validation: app drives `invalid` and `errorMessage` itself
+// (e.g. for server-returned business-rule violations)
+ToggleSwitch twoFactor = new ToggleSwitch("Two-factor authentication required");
+twoFactor.setManualValidation(true);
+
+binder.forField(twoFactor)
+ .withValidator((value, context) -> {
+ if (account.isCompliancePlan() && !value) {
+ return ValidationResult.error(
+ "Two-factor authentication can't be disabled on the Compliance plan");
+ }
+ return ValidationResult.ok();
+ })
+ .bind(User::isTwoFactorRequired, User::setTwoFactorRequired);
+
+// Application-level revalidation runs through Binder; the protected
+// validate() method on the component itself is invoked by the framework.
+binder.validate();
+```
+
+**Why this shape:** Required-handling via `setRequiredIndicatorVisible` plus a default validator that fails on the empty value (`Boolean.FALSE`) is exactly how `Checkbox` does it — consistent across all binary Vaadin fields. `HasValidationProperties` provides `setErrorMessage` / `setInvalid`. `HasValidator` provides `setManualValidation`; `validate()` itself is `protected` (as on `Checkbox`) — applications that need to drive validation imperatively go through `Binder.validate()`. The nested `ToggleSwitchI18n` class mirrors `CheckboxI18n` exactly: one fluent setter for the default required-error message, `Serializable`, retrievable via `getI18n()`. Custom `setErrorMessage(String)` takes priority over the i18n message (matches Checkbox semantics). The empty value of the field is `Boolean.FALSE`, also matching Checkbox — so `Binder` `asRequired()` and the toggle switch's own required validation agree on which state counts as "empty".
+
+---
+
+## 7. Helper text
+
+Covers requirement(s): 8
+
+```java
+ToggleSwitch autosave = new ToggleSwitch("Auto-save");
+autosave.setHelperText("Save changes automatically every 30 seconds");
+add(autosave);
+
+// HTML helper content (links, formatting)
+ToggleSwitch beta = new ToggleSwitch("Beta features");
+beta.setHelperComponent(new Anchor("/beta", "See what's enabled in the beta program"));
+```
+
+**Why this shape:** `setHelperText(String)` and `setHelperComponent(Component)` come from `HasHelper` (transitive via `InputField`) — same surface every Vaadin field already exposes.
+
+---
+
+## 8. Tooltip
+
+Covers requirement(s): 10
+
+```java
+ToggleSwitch active = new ToggleSwitch("Active");
+active.setTooltipText("Last delivery: 2 minutes ago");
+
+// Markdown-formatted tooltip
+active.setTooltipMarkdown("Last delivery: **2 minutes ago**");
+
+// Direct access to the tooltip handle for advanced configuration (position, etc.)
+active.getTooltip().setPosition(Tooltip.TooltipPosition.TOP);
+```
+
+**Why this shape:** `setTooltipText`, `setTooltipMarkdown`, and `getTooltip()` come from `HasTooltip` (transitive via `InputField`) — same surface every Vaadin field component already exposes. No component-specific tooltip method.
+
+---
+
+## 9. Form integration via Binder
+
+Covers requirement(s): 11
+
+```java
+public class TwoFactorForm extends FormLayout {
+ private final ToggleSwitch twoFactor = new ToggleSwitch("Two-factor authentication required");
+ private final TextField name = new TextField("Name");
+ private final Binder binder = new Binder<>(User.class);
+
+ public TwoFactorForm() {
+ add(name, twoFactor);
+
+ binder.forField(twoFactor)
+ .bind(User::isTwoFactorRequired, User::setTwoFactorRequired);
+ binder.forField(name)
+ .bind(User::getName, User::setName);
+ }
+
+ public void edit(User user) {
+ binder.readBean(user); // hydrate the form — value-change events fire with isFromClient=false
+ }
+
+ public void save(User user) {
+ if (binder.writeBeanIfValid(user)) {
+ userService.save(user);
+ }
+ }
+
+ public void cancel(User original) {
+ binder.readBean(original); // revert: switch flips back, no isFromClient=true events
+ }
+}
+```
+
+**Why this shape:** Flow does not use native HTML form submission; the Vaadin equivalent of "name + value submitted on `