diff --git a/core/src/components/checkbox/checkbox.scss b/core/src/components/checkbox/checkbox.scss
index 438add273d0..b99260dd8ed 100644
--- a/core/src/components/checkbox/checkbox.scss
+++ b/core/src/components/checkbox/checkbox.scss
@@ -1,4 +1,3 @@
-@import "../../themes/ionic.globals";
@import "./checkbox.vars.scss";
// Checkbox
@@ -119,9 +118,16 @@ input {
display: none;
}
+// Hide the native focus ring since we draw our own focus indicator.
+:host(:focus) {
+ outline: none;
+}
+
.native-wrapper {
display: flex;
+ position: relative;
+
align-items: center;
}
@@ -152,6 +158,28 @@ input {
opacity: 0;
}
+// Checkbox: Keyboard Focus
+// ---------------------------------------------
+
+:host(.ion-focused) .native-wrapper::after {
+ @include border-radius(50%);
+
+ display: block;
+ position: absolute;
+
+ width: $checkbox-focus-ring-size;
+ height: $checkbox-focus-ring-size;
+
+ background: $checkbox-background-color-focused;
+
+ content: "";
+ opacity: 0.2;
+
+ // Centered with insets because `transform` is physical and breaks in RTL.
+ inset-block-start: calc(50% - #{$checkbox-focus-ring-size} / 2);
+ inset-inline-start: calc(50% - #{$checkbox-focus-ring-size} / 2);
+}
+
// Checkbox Bottom Content
// ----------------------------------------------------------------
diff --git a/core/src/components/checkbox/checkbox.tsx b/core/src/components/checkbox/checkbox.tsx
index 99cb13c474a..a13bb3580be 100644
--- a/core/src/components/checkbox/checkbox.tsx
+++ b/core/src/components/checkbox/checkbox.tsx
@@ -1,6 +1,6 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
-import { Build, Component, Element, Event, Host, Method, Prop, State, h } from '@stencil/core';
-import { checkInvalidState } from '@utils/forms';
+import { Build, Component, Element, Event, Host, Method, Prop, State, forceUpdate, h } from '@stencil/core';
+import { checkInvalidState, createItemMultipleInputsObserver } from '@utils/forms';
import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes, renderHiddenInput } from '@utils/helpers';
import { createColorClasses, hostContext } from '@utils/theme';
@@ -37,6 +37,7 @@ export class Checkbox implements ComponentInterface {
private errorTextId = `${this.inputId}-error-text`;
private inheritedAttributes: Attributes = {};
private validationObserver?: MutationObserver;
+ private itemFocusObserver?: MutationObserver;
@Element() el!: HTMLIonCheckboxElement;
@@ -199,6 +200,8 @@ export class Checkbox implements ComponentInterface {
// Always set initial state
this.isInvalid = checkInvalidState(el);
this.hasLabelContent = this.el.textContent !== '';
+
+ this.itemFocusObserver = createItemMultipleInputsObserver(el, () => forceUpdate(this));
}
componentWillLoad() {
@@ -210,11 +213,14 @@ export class Checkbox implements ComponentInterface {
}
disconnectedCallback() {
- // Clean up validation observer to prevent memory leaks.
if (this.validationObserver) {
this.validationObserver.disconnect();
this.validationObserver = undefined;
}
+ if (this.itemFocusObserver) {
+ this.itemFocusObserver.disconnect();
+ this.itemFocusObserver = undefined;
+ }
}
/** @internal */
@@ -338,6 +344,8 @@ export class Checkbox implements ComponentInterface {
} = this;
const mode = getIonMode(this);
const path = getSVGPath(mode, indeterminate);
+ const inItem = hostContext('ion-item', el);
+ const inMultipleInputsItem = hostContext('ion-item.item-multiple-inputs', el);
renderHiddenInput(true, el, name, checked ? value : '', disabled);
@@ -360,11 +368,14 @@ export class Checkbox implements ComponentInterface {
onClick={this.onClick}
class={createColorClasses(color, {
[mode]: true,
- 'in-item': hostContext('ion-item', el),
+ 'in-item': inItem,
'checkbox-checked': checked,
'checkbox-disabled': disabled,
'checkbox-indeterminate': indeterminate,
interactive: true,
+ // A single-input item has the input cover and draws the indicator itself.
+ // A multi-input item has no cover, so each control draws its own.
+ 'ion-focusable': !inItem || inMultipleInputsItem,
[`checkbox-justify-${justify}`]: justify !== undefined,
[`checkbox-alignment-${alignment}`]: alignment !== undefined,
[`checkbox-label-placement-${labelPlacement}`]: true,
diff --git a/core/src/components/checkbox/checkbox.vars.scss b/core/src/components/checkbox/checkbox.vars.scss
index 8c73dff6f8f..551eb479b38 100644
--- a/core/src/components/checkbox/checkbox.vars.scss
+++ b/core/src/components/checkbox/checkbox.vars.scss
@@ -1,5 +1,12 @@
+@import "../../themes/ionic.globals";
+
/// @prop - Top margin of checkbox's label when in an item
$checkbox-item-label-margin-top: 10px;
/// @prop - Bottom margin of checkbox's label when in an item
-$checkbox-item-label-margin-bottom: 10px;
\ No newline at end of file
+$checkbox-item-label-margin-bottom: 10px;
+
+/// @prop - Background color of focus indicator for checkbox when focused
+$checkbox-background-color-focused: ion-color(primary, tint);
+
+$checkbox-focus-ring-size: 36px;
diff --git a/core/src/components/checkbox/test/a11y/checkbox.e2e.ts b/core/src/components/checkbox/test/a11y/checkbox.e2e.ts
index 6d0b6645ae5..989a7f97a96 100644
--- a/core/src/components/checkbox/test/a11y/checkbox.e2e.ts
+++ b/core/src/components/checkbox/test/a11y/checkbox.e2e.ts
@@ -25,6 +25,64 @@ configs({ directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ title,
});
});
+/**
+ * These assert `ion-focusable`, not the rendered ring, because `ion-focused`
+ * relies on keyboard-mode detection that is flaky on WebKit. Gating is mode-independent.
+ */
+configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, config }) => {
+ test.describe(title('checkbox: focus indicator'), () => {
+ test('standalone checkbox should be focusable', async ({ page }) => {
+ await page.setContent(
+ `
+
+ Checkbox
+
+ `,
+ config
+ );
+
+ const checkbox = page.locator('ion-checkbox');
+ await expect(checkbox).toHaveClass(/ion-focusable/);
+ });
+
+ test('checkbox in a single-input item should not show its own focus indicator', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ Checkbox
+
+
+ `,
+ config
+ );
+
+ const checkbox = page.locator('ion-checkbox');
+ const item = page.locator('ion-item');
+ await expect(checkbox).not.toHaveClass(/ion-focusable/);
+ await expect(item).toHaveClass(/ion-focusable/);
+ });
+
+ test('checkbox in a multi-input item should be focusable', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ Checkbox 1
+ Checkbox 2
+
+
+ `,
+ config
+ );
+
+ const checkboxes = page.locator('ion-checkbox');
+ await expect(checkboxes.nth(0)).toHaveClass(/ion-focusable/);
+ await expect(checkboxes.nth(1)).toHaveClass(/ion-focusable/);
+ });
+ });
+});
+
configs({ directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
test.describe(title('checkbox: a11y'), () => {
test.describe(title('checkbox: font scaling'), () => {
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts b/core/src/components/checkbox/test/basic/checkbox.e2e.ts
index 733f229e4b6..c9b74fb6d2e 100644
--- a/core/src/components/checkbox/test/basic/checkbox.e2e.ts
+++ b/core/src/components/checkbox/test/basic/checkbox.e2e.ts
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
-import { configs, test } from '@utils/test/playwright';
+import { applyKeyboardFocus, configs, test } from '@utils/test/playwright';
configs().forEach(({ title, screenshot, config }) => {
test.describe(title('checkbox: basic visual tests'), () => {
@@ -47,7 +47,7 @@ configs().forEach(({ title, screenshot, config }) => {
/**
* This behavior does not vary across modes/directions
*/
-configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
+configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('checkbox: ionChange'), () => {
test('should fire ionChange when interacting with checkbox', async ({ page }) => {
await page.setContent(
@@ -138,48 +138,6 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
});
test.describe(title('checkbox: ionFocus'), () => {
- test('should not have visual regressions', async ({ page, pageUtils }) => {
- await page.setContent(
- `
-
-
-
- Unchecked
-
- `,
- config
- );
-
- await pageUtils.pressKeys('Tab');
-
- const container = page.locator('#container');
-
- await expect(container).toHaveScreenshot(screenshot(`checkbox-focus`));
- });
-
- test('should not have visual regressions when interacting with checkbox in item', async ({ page, pageUtils }) => {
- await page.setContent(
- `
-
- Unchecked
-
- `,
- config
- );
-
- // Test focus with keyboard navigation
- await pageUtils.pressKeys('Tab');
-
- const item = page.locator('ion-item');
-
- await expect(item).toHaveScreenshot(screenshot(`checkbox-in-item-focus`));
- });
-
test('should fire ionFocus when checkbox is focused', async ({ page, pageUtils }) => {
await page.setContent(
`
@@ -328,3 +286,67 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
});
});
});
+
+/**
+ * This behavior does not vary across directions
+ */
+configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
+ test.describe(title('checkbox: focus visual'), () => {
+ test('should render focus indicator when unchecked', async ({ page }) => {
+ // `ion-app` is required so `startFocusVisible` applies `ion-focused` on keyboard focus.
+ await page.setContent(
+ `
+
+
+
+
+ Unchecked
+
+
+ `,
+ config
+ );
+
+ const checkbox = page.locator('ion-checkbox');
+
+ await applyKeyboardFocus(page, checkbox);
+
+ const container = page.locator('#container');
+
+ await expect(container).toHaveScreenshot(screenshot(`checkbox-focus`));
+ });
+
+ test('should render focus indicator when checked', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+
+ Checked
+
+
+ `,
+ config
+ );
+
+ const checkbox = page.locator('ion-checkbox');
+
+ await applyKeyboardFocus(page, checkbox);
+
+ const container = page.locator('#container');
+
+ await expect(container).toHaveScreenshot(screenshot(`checkbox-focus-checked`));
+ });
+ });
+});
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..9e8caf8ee43
Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..a24d128c04b
Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..5f268bb2df0
Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..769ebd955ad
Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..b794065ad98
Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..e96f191e15d
Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png
index ae5bed12337..86c807e630c 100644
Binary files a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png
index 305f4d89e1b..e328672a417 100644
Binary files a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png
index 637fa09bb1e..fdfb755ea67 100644
Binary files a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..112e7ada86f
Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..b3cc4169e01
Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..5c86d926c0e
Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png
deleted file mode 100644
index 3c134cb37f8..00000000000
Binary files a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png and /dev/null differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png
deleted file mode 100644
index 9dc5aed80d8..00000000000
Binary files a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png and /dev/null differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts b/core/src/components/checkbox/test/item/checkbox.e2e.ts
index 341839739b1..1070f8a4c3c 100644
--- a/core/src/components/checkbox/test/item/checkbox.e2e.ts
+++ b/core/src/components/checkbox/test/item/checkbox.e2e.ts
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
-import { configs, test } from '@utils/test/playwright';
+import { applyKeyboardFocus, configs, test } from '@utils/test/playwright';
configs().forEach(({ title, screenshot, config }) => {
test.describe(title('checkbox: item with list'), () => {
@@ -126,6 +126,70 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
await expect(list).toHaveScreenshot(screenshot(`checkbox-stacked-label-in-item`));
});
});
+
+ test.describe(title('checkbox: multiple inputs in item'), () => {
+ test('should not have visual regressions with multiple checkboxes in an item', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ Checkbox 1
+ Checkbox 2
+
+
+ `,
+ config
+ );
+ const list = page.locator('ion-list');
+ await expect(list).toHaveScreenshot(screenshot(`checkbox-multiple-in-item`));
+ });
+ });
+});
+
+/**
+ * This behavior does not vary across directions
+ */
+configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
+ test.describe(title('checkbox: focus in item'), () => {
+ test('should render focus indicator in an item', async ({ page, pageUtils }) => {
+ await page.setContent(
+ `
+
+ Unchecked
+
+ `,
+ config
+ );
+
+ await pageUtils.pressKeys('Tab');
+
+ const item = page.locator('ion-item');
+
+ await expect(item).toHaveScreenshot(screenshot(`checkbox-in-item-focus`));
+ });
+
+ test('should render focus indicator for a checkbox in a multi-input item', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ Checkbox 1
+ Checkbox 2
+
+
+ `,
+ config
+ );
+
+ const checkbox = page.locator('ion-checkbox').first();
+
+ await applyKeyboardFocus(page, checkbox);
+
+ const item = page.locator('ion-item');
+
+ await expect(item).toHaveScreenshot(screenshot(`checkbox-multiple-in-item-focus`));
+ });
+ });
});
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..b7c449e6f9e
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png
similarity index 100%
rename from core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png
rename to core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..b465b40ad93
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..40d72cf339f
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..3101f22b855
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..247aba9eb13
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..3d9e7f4f7a8
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..d7fc1740783
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..987bc5d9b19
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..e919e470843
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..d5a636afb4a
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..393c0e0bcd6
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..de660abf936
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..f5f925dabe6
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..40be000b83a
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..82895bd4516
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..723a11e9d84
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..c5297bc2d64
Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/checkbox/test/item/index.html b/core/src/components/checkbox/test/item/index.html
index 2548cbfaad0..60582e73abc 100644
--- a/core/src/components/checkbox/test/item/index.html
+++ b/core/src/components/checkbox/test/item/index.html
@@ -15,7 +15,7 @@
+
+
+
+
+ Unchecked
+
+
+
+ `,
+ config
+ );
+
+ const radio = page.locator('ion-radio');
+
+ await applyKeyboardFocus(page, radio);
+
+ const container = page.locator('#container');
+
+ await expect(container).toHaveScreenshot(screenshot(`radio-focus`));
+ });
+
+ test('should render focus indicator when checked', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+
+
+ Checked
+
+
+
+ `,
+ config
+ );
+
+ const radio = page.locator('ion-radio');
+
+ await applyKeyboardFocus(page, radio);
+
+ const container = page.locator('#container');
+
+ await expect(container).toHaveScreenshot(screenshot(`radio-focus-checked`));
+ });
+ });
+});
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..5ff92f5d5e3
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..ab13bbaa13c
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..f8c844b5351
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..af756eb1be7
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..c0e80459e81
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Safari-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..e1f652a322a
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..749bbf84999
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..6a7c560e590
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..595e0f81d24
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..828d7ce2f16
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..ff068041cb7
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..a89aa121456
Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/radio/test/item/index.html b/core/src/components/radio/test/item/index.html
index 36e46779fd2..e33e518d156 100644
--- a/core/src/components/radio/test/item/index.html
+++ b/core/src/components/radio/test/item/index.html
@@ -15,7 +15,7 @@
-
-
- Unchecked
-
- `,
- config
- );
-
- await pageUtils.pressKeys('Tab');
-
- const container = page.locator('#container');
-
- await expect(container).toHaveScreenshot(screenshot(`toggle-focus`));
- });
-
- test('should not have visual regressions when interacting with toggle in item', async ({ page, pageUtils }) => {
- await page.setContent(
- `
-
- Unchecked
-
- `,
- config
- );
-
- // Test focus with keyboard navigation
- await pageUtils.pressKeys('Tab');
-
- const item = page.locator('ion-item');
-
- await expect(item).toHaveScreenshot(screenshot(`toggle-in-item-focus`));
- });
-
test('should fire ionFocus when toggle is focused', async ({ page, pageUtils }) => {
await page.setContent(
`
@@ -285,3 +243,67 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
});
});
});
+
+/**
+ * This behavior does not vary across directions
+ */
+configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
+ test.describe(title('toggle: focus visual'), () => {
+ test('should render focus indicator when unchecked', async ({ page }) => {
+ // `ion-app` is required so `startFocusVisible` applies `ion-focused` on keyboard focus.
+ await page.setContent(
+ `
+
+
+
+
+ Unchecked
+
+
+ `,
+ config
+ );
+
+ const toggle = page.locator('ion-toggle');
+
+ await applyKeyboardFocus(page, toggle);
+
+ const container = page.locator('#container');
+
+ await expect(container).toHaveScreenshot(screenshot(`toggle-focus`));
+ });
+
+ test('should render focus indicator when checked', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+
+ Checked
+
+
+ `,
+ config
+ );
+
+ const toggle = page.locator('ion-toggle');
+
+ await applyKeyboardFocus(page, toggle);
+
+ const container = page.locator('#container');
+
+ await expect(container).toHaveScreenshot(screenshot(`toggle-focus-checked`));
+ });
+ });
+});
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..f5090dcb232
Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..c2afe2f5941
Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..08176dd7004
Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..2b0432e046b
Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..800164b9640
Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..20b9c1f27e1
Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Chrome-linux.png
index 8cfaef24db3..26dc586af8e 100644
Binary files a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Firefox-linux.png
index bb21be2862e..856c3679238 100644
Binary files a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Safari-linux.png
index c20935c06b8..8153ee087f6 100644
Binary files a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..a82ee6c5875
Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..8bc4ddfa407
Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..bc2c99f8353
Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png
deleted file mode 100644
index 7d5347249a8..00000000000
Binary files a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png and /dev/null differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png
deleted file mode 100644
index 19bd30c2be0..00000000000
Binary files a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png and /dev/null differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts b/core/src/components/toggle/test/color/toggle.e2e.ts
index cdebe75ec79..871cc316fb4 100644
--- a/core/src/components/toggle/test/color/toggle.e2e.ts
+++ b/core/src/components/toggle/test/color/toggle.e2e.ts
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
-import { configs, test } from '@utils/test/playwright';
+import { applyKeyboardFocus, configs, test } from '@utils/test/playwright';
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('toggle: color'), () => {
@@ -26,5 +26,62 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
const toggle = page.locator('ion-toggle');
await expect(toggle).toHaveScreenshot(screenshot(`toggle-color-unchecked`));
});
+
+ test('should apply color to the focus indicator when checked', async ({ page }) => {
+ // `ion-app` is required so `startFocusVisible` applies `ion-focused` on keyboard focus.
+ await page.setContent(
+ `
+
+
+
+
+ Label
+
+
+ `,
+ config
+ );
+
+ const toggle = page.locator('ion-toggle');
+
+ await applyKeyboardFocus(page, toggle);
+
+ const container = page.locator('#container');
+
+ await expect(container).toHaveScreenshot(screenshot(`toggle-color-focus-checked`));
+ });
+
+ test('should not apply color to the focus indicator when unchecked', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+
+ Label
+
+
+ `,
+ config
+ );
+
+ const toggle = page.locator('ion-toggle');
+
+ await applyKeyboardFocus(page, toggle);
+
+ const container = page.locator('#container');
+
+ await expect(container).toHaveScreenshot(screenshot(`toggle-color-focus-unchecked`));
+ });
});
});
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..5649ce582d9
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..1c859ee105d
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..d2d2651e795
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..de6ed72d4be
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..f8ef411ff1a
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..168808ae7e1
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..ed28f85c278
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..2058c3d5e69
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..ae7633dd651
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..476ba9d5097
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..5ff96d60a1c
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..fe6f1b21513
Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts b/core/src/components/toggle/test/item/toggle.e2e.ts
index 866a382494f..cc549b9a6bc 100644
--- a/core/src/components/toggle/test/item/toggle.e2e.ts
+++ b/core/src/components/toggle/test/item/toggle.e2e.ts
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
-import { configs, test } from '@utils/test/playwright';
+import { applyKeyboardFocus, configs, test } from '@utils/test/playwright';
configs().forEach(({ title, screenshot, config }) => {
test.describe(title('toggle: item'), () => {
@@ -110,6 +110,104 @@ configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, screenshot, co
});
});
+/**
+ * The focus indicator differs between iOS and MD, so these run in both modes.
+ * This behavior does not vary across directions.
+ */
+configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
+ test.describe(title('toggle: focus in item'), () => {
+ test('should render focus indicator in an item', async ({ page }) => {
+ // The item gets `ion-focused` too, which is what suppresses the toggle's indicator.
+ await page.setContent(
+ `
+
+
+ Unchecked
+
+
+ `,
+ config
+ );
+
+ const toggle = page.locator('ion-toggle');
+
+ await applyKeyboardFocus(page, toggle);
+
+ const item = page.locator('ion-item');
+
+ await expect(item).toHaveScreenshot(screenshot(`toggle-in-item-focus`));
+ });
+
+ test('should render focus indicator for a checked toggle with a color in an item', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ Checked
+
+
+ `,
+ config
+ );
+
+ const toggle = page.locator('ion-toggle');
+
+ await applyKeyboardFocus(page, toggle);
+
+ const item = page.locator('ion-item');
+
+ await expect(item).toHaveScreenshot(screenshot(`toggle-color-checked-in-item-focus`));
+ });
+
+ test('should render focus indicator for a toggle in a clickable item', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ Unchecked
+
+
+ `,
+ config
+ );
+
+ const toggle = page.locator('ion-toggle');
+
+ await applyKeyboardFocus(page, toggle);
+
+ const item = page.locator('ion-item');
+
+ await expect(item).toHaveScreenshot(screenshot(`toggle-in-clickable-item-focus`));
+ });
+
+ test('should render focus indicator for a toggle in a multi-input item', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ Toggle 1
+ Toggle 2
+
+
+ `,
+ config
+ );
+
+ const item = page.locator('ion-item');
+
+ // The item adds this after its controls render, and the toggles re-render off
+ // it, so waiting avoids capturing the pre-settle state.
+ await expect(item).toHaveClass(/item-multiple-inputs/);
+
+ const toggle = page.locator('ion-toggle').first();
+
+ await applyKeyboardFocus(page, toggle);
+
+ await expect(item).toHaveScreenshot(screenshot(`toggle-multiple-in-item-focus`));
+ });
+ });
+});
+
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('toggle: item functionality'), () => {
test('clicking padded space within item should click the toggle', async ({ page }) => {
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..fc51ac7b706
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..e5c55ebc3ca
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..83caa41401a
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..092815e8974
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..ff8aaa150dd
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..7f87fd20447
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..416897ad19f
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..d169f66fe58
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..113825e84de
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..d431fca3f0d
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..80da04f99fc
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..6fb39e31043
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..e356b738007
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Firefox-linux.png
similarity index 100%
rename from core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Firefox-linux.png
rename to core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Firefox-linux.png
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..50bca49651c
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..8e16c7a4b8f
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..0743e8f1314
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..84610944bd1
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..9cac802f1b9
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..07bfcfdf0ea
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..7cd37793433
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..1342c03b461
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..78a2b693fcc
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..ae4b62ff354
Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/toggle/toggle.ios.scss b/core/src/components/toggle/toggle.ios.scss
index cfe12f1d5b0..5f9a9cb6b15 100644
--- a/core/src/components/toggle/toggle.ios.scss
+++ b/core/src/components/toggle/toggle.ios.scss
@@ -171,3 +171,20 @@
:host(.toggle-disabled) {
opacity: $toggle-ios-disabled-opacity;
}
+
+// iOS Toggle: Keyboard Focus
+// ----------------------------------------------------------
+// `toggle-defers-indicator` is excluded rather than overridden, because an
+// override would have to out-specify every rule below, including `.ion-color`.
+
+:host(.ion-focused:not(.toggle-defers-indicator)) .toggle-icon {
+ box-shadow: 0 0 0 4px $toggle-focus-ring-color-off;
+}
+
+:host(.ion-focused.toggle-checked:not(.toggle-defers-indicator)) .toggle-icon {
+ box-shadow: 0 0 0 4px $toggle-focus-ring-color;
+}
+
+:host(.ion-color.ion-focused.toggle-checked:not(.toggle-defers-indicator)) .toggle-icon {
+ box-shadow: 0 0 0 4px current-color(base, $toggle-focus-ring-color-alpha);
+}
diff --git a/core/src/components/toggle/toggle.md.scss b/core/src/components/toggle/toggle.md.scss
index d0a3bfda360..cd0b5582059 100644
--- a/core/src/components/toggle/toggle.md.scss
+++ b/core/src/components/toggle/toggle.md.scss
@@ -15,7 +15,8 @@
--handle-width: #{$toggle-md-handle-width};
--handle-height: #{$toggle-md-handle-height};
--handle-max-height: #{$toggle-md-handle-max-height};
- --handle-spacing: 0;
+ // A length, not a bare 0, so it composes in `calc()`.
+ --handle-spacing: 0px;
--handle-transition: #{$toggle-md-transition};
}
@@ -67,6 +68,69 @@
height: 100%;
}
+// Material Design Toggle: Keyboard Focus
+// ----------------------------------------------------------
+// The indicator is drawn on the wrapper because the handle uses
+// `contain: strict`, which would clip it. The wrapper travels with the handle.
+//
+// `toggle-defers-indicator` is excluded rather than overridden so the indicator
+// is never created, leaving the rules that position and color it nothing to act on.
+
+:host(.ion-focused:not(.toggle-defers-indicator)) .toggle-icon-wrapper::after {
+ @include border-radius(50%);
+
+ display: block;
+ position: absolute;
+
+ width: $toggle-md-focus-ring-size;
+ height: $toggle-md-focus-ring-size;
+
+ transform: translateY(-50%);
+
+ background: $toggle-focus-ring-color-off;
+
+ content: "";
+
+ // Drawn behind the handle so the translucent indicator does not tint it. The
+ // wrapper is its own stacking context, so it stays above the track.
+ z-index: -1;
+
+ inset-block-start: 50%;
+}
+
+// Positioned the same way as `.toggle-inner`, offset by half the size difference
+// so it centers on the handle. Anchored physically to mirror the handle, which
+// uses `left` in LTR and `right` in RTL.
+
+:host(.toggle-ltr.ion-focused) .toggle-icon-wrapper::after {
+ // stylelint-disable-next-line property-disallowed-list
+ left: calc(var(--handle-spacing) + (var(--handle-width) - #{$toggle-md-focus-ring-size}) / 2);
+}
+
+:host(.toggle-rtl.ion-focused) .toggle-icon-wrapper::after {
+ // stylelint-disable-next-line property-disallowed-list
+ right: calc(var(--handle-spacing) + (var(--handle-width) - #{$toggle-md-focus-ring-size}) / 2);
+}
+
+// Matches the checked shift `.toggle-inner` applies, so the indicator stays on
+// the handle. The Y component repeats the centering from the base rule.
+
+:host(.toggle-ltr.ion-focused.toggle-checked) .toggle-icon-wrapper::after {
+ transform: translate(calc(var(--handle-spacing) * -2), -50%);
+}
+
+:host(.toggle-rtl.ion-focused.toggle-checked) .toggle-icon-wrapper::after {
+ transform: translate(calc(var(--handle-spacing) * 2), -50%);
+}
+
+:host(.ion-focused.toggle-checked) .toggle-icon-wrapper::after {
+ background: $toggle-focus-ring-color;
+}
+
+:host(.ion-color.ion-focused.toggle-checked) .toggle-icon-wrapper::after {
+ background: current-color(base, $toggle-focus-ring-color-alpha);
+}
+
// Material Design Toggle: Disabled
// ----------------------------------------------------------
// The toggle and label should use the
diff --git a/core/src/components/toggle/toggle.md.vars.scss b/core/src/components/toggle/toggle.md.vars.scss
index 63f9ef72c9a..b782131b8be 100644
--- a/core/src/components/toggle/toggle.md.vars.scss
+++ b/core/src/components/toggle/toggle.md.vars.scss
@@ -48,3 +48,5 @@ $toggle-md-on-off-label-color: #000;
/// @prop - The text color of the on/off labels when the toggle is checked
$toggle-md-on-off-label-checked-color: #fff;
+
+$toggle-md-focus-ring-size: 36px;
diff --git a/core/src/components/toggle/toggle.scss b/core/src/components/toggle/toggle.scss
index b64107e66b0..1c239104656 100644
--- a/core/src/components/toggle/toggle.scss
+++ b/core/src/components/toggle/toggle.scss
@@ -1,4 +1,3 @@
-@import "../../themes/ionic.globals";
@import "./toggle.vars.scss";
// Toggle
@@ -59,10 +58,6 @@
width: auto;
}
-:host(.ion-focused) input {
- border: 2px solid #5e9ed6;
-}
-
:host(.toggle-disabled) {
pointer-events: none;
}
@@ -75,6 +70,11 @@ input {
display: none;
}
+// Hide the native focus ring since we draw our own focus indicator.
+:host(:focus) {
+ outline: none;
+}
+
// Toggle Wrapper
// --------------------------------------------------
diff --git a/core/src/components/toggle/toggle.tsx b/core/src/components/toggle/toggle.tsx
index b46635c8a78..041a200bead 100644
--- a/core/src/components/toggle/toggle.tsx
+++ b/core/src/components/toggle/toggle.tsx
@@ -1,6 +1,6 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
-import { Build, Component, Element, Event, Host, Prop, State, Watch, h } from '@stencil/core';
-import { checkInvalidState } from '@utils/forms';
+import { Build, Component, Element, Event, Host, Prop, State, Watch, forceUpdate, h } from '@stencil/core';
+import { checkInvalidState, createItemMultipleInputsObserver } from '@utils/forms';
import { renderHiddenInput, inheritAriaAttributes } from '@utils/helpers';
import type { Attributes } from '@utils/helpers';
import { hapticSelection } from '@utils/native/haptic';
@@ -46,6 +46,7 @@ export class Toggle implements ComponentInterface {
private toggleTrack?: HTMLElement;
private didLoad = false;
private validationObserver?: MutationObserver;
+ private itemFocusObserver?: MutationObserver;
@Element() el!: HTMLIonToggleElement;
@@ -227,6 +228,11 @@ export class Toggle implements ComponentInterface {
// Always set initial state
this.isInvalid = checkInvalidState(el);
+
+ this.itemFocusObserver = createItemMultipleInputsObserver(el, () => forceUpdate(this), [
+ 'item-multiple-inputs',
+ 'ion-activatable',
+ ]);
}
componentDidLoad() {
@@ -258,6 +264,11 @@ export class Toggle implements ComponentInterface {
this.gesture = undefined;
}
+ if (this.itemFocusObserver) {
+ this.itemFocusObserver.disconnect();
+ this.itemFocusObserver = undefined;
+ }
+
// Clean up validation observer to prevent memory leaks.
if (this.validationObserver) {
this.validationObserver.disconnect();
@@ -456,6 +467,12 @@ export class Toggle implements ComponentInterface {
const mode = getIonMode(this);
const value = this.getValue();
const rtl = isRTL(el) ? 'rtl' : 'ltr';
+ const inItem = hostContext('ion-item', el);
+ const inMultipleInputsItem = hostContext('ion-item.item-multiple-inputs', el);
+ // A clickable item is a second tab stop painting the same row indicator, which
+ // `item-multiple-inputs` misses because it counts cover elements, not toggles.
+ // The attributes are matched too because the class needs the item to render.
+ const inClickableItem = hostContext('ion-item.ion-activatable, ion-item[button], ion-item[href]', el);
renderHiddenInput(true, el, name, checked ? value : '', disabled);
return (
@@ -475,7 +492,11 @@ export class Toggle implements ComponentInterface {
onBlur={this.onBlur}
class={createColorClasses(color, {
[mode]: true,
- 'in-item': hostContext('ion-item', el),
+ 'in-item': inItem,
+ // `ion-focusable` has to stay on because it is what makes the item focusable.
+ // When the item draws the row indicator, CSS suppresses ours instead.
+ 'ion-focusable': true,
+ 'toggle-defers-indicator': inItem && !inMultipleInputsItem && !inClickableItem,
'toggle-activated': activated,
'toggle-checked': checked,
'toggle-disabled': disabled,
diff --git a/core/src/components/toggle/toggle.vars.scss b/core/src/components/toggle/toggle.vars.scss
index 4f0d34a764f..173de9c3684 100644
--- a/core/src/components/toggle/toggle.vars.scss
+++ b/core/src/components/toggle/toggle.vars.scss
@@ -1,5 +1,16 @@
+@import "../../themes/ionic.globals";
+
/// @prop - Top margin of toggle's label when in an item
$toggle-item-label-margin-top: 10px;
/// @prop - Bottom margin of toggle's label when in an item
-$toggle-item-label-margin-bottom: 10px;
\ No newline at end of file
+$toggle-item-label-margin-bottom: 10px;
+
+/// @prop - Color of the focus indicator for the unchecked toggle
+$toggle-focus-ring-color-off: rgba(var(--ion-text-color-rgb, 0, 0, 0), .12);
+
+/// @prop - Alpha of the focus indicator for the checked toggle
+$toggle-focus-ring-color-alpha: .2;
+
+/// @prop - Color of the focus indicator for the checked toggle
+$toggle-focus-ring-color: ion-color(primary, base, $toggle-focus-ring-color-alpha);
diff --git a/core/src/utils/forms/index.ts b/core/src/utils/forms/index.ts
index 682811ed643..d54258b956b 100644
--- a/core/src/utils/forms/index.ts
+++ b/core/src/utils/forms/index.ts
@@ -1,3 +1,4 @@
export * from './notch-controller';
export * from './compare-with-utils';
+export * from './item-multiple-inputs';
export * from './validity';
diff --git a/core/src/utils/forms/item-multiple-inputs.ts b/core/src/utils/forms/item-multiple-inputs.ts
new file mode 100644
index 00000000000..0678456ebf5
--- /dev/null
+++ b/core/src/utils/forms/item-multiple-inputs.ts
@@ -0,0 +1,42 @@
+import { Build } from '@stencil/core';
+
+/**
+ * `item-multiple-inputs` is toggled on the item after its form controls render,
+ * so a control whose focus styling depends on it needs to re-render when it flips.
+ *
+ * @internal
+ * @param el The form control whose closest `ion-item` should be observed.
+ * @param onChange Called whenever one of `classNames` is added or removed.
+ * @param classNames The item classes the caller's rendering depends on. A caller
+ * reading more than one must list them all, or changes to the rest are ignored.
+ * @returns The observer to disconnect in `disconnectedCallback`, or `undefined`
+ * if it could not be created.
+ */
+export const createItemMultipleInputsObserver = (
+ el: HTMLElement,
+ onChange: () => void,
+ classNames: string[] = ['item-multiple-inputs']
+): MutationObserver | undefined => {
+ const item = el.closest('ion-item');
+
+ if (!item || !Build.isBrowser || typeof MutationObserver === 'undefined') {
+ return undefined;
+ }
+
+ const readClasses = () => classNames.map((name) => item.classList.contains(name)).join(',');
+
+ let previousClasses = readClasses();
+
+ const observer = new MutationObserver(() => {
+ const currentClasses = readClasses();
+
+ if (currentClasses !== previousClasses) {
+ previousClasses = currentClasses;
+ onChange();
+ }
+ });
+
+ observer.observe(item, { attributes: true, attributeFilter: ['class'] });
+
+ return observer;
+};
diff --git a/core/src/utils/test/playwright/apply-keyboard-focus.ts b/core/src/utils/test/playwright/apply-keyboard-focus.ts
new file mode 100644
index 00000000000..7c7ff90dc3e
--- /dev/null
+++ b/core/src/utils/test/playwright/apply-keyboard-focus.ts
@@ -0,0 +1,22 @@
+import type { Locator } from '@playwright/test';
+import { expect } from '@playwright/test';
+
+import type { E2EPage } from './playwright-declarations';
+
+/**
+ * Puts an element into the keyboard focus state that draws the focus indicator.
+ * Requires an `ion-app` so `startFocusVisible` is running. Its listeners attach
+ * asynchronously, so focus is retried until `ion-focused` sticks.
+ *
+ * `Shift` re-enables keyboard mode without moving focus, in case a pointer event
+ * earlier in the test turned it off. Focus moves programmatically because Firefox
+ * cannot move it back into the page once blurred, so an early Tab is unretryable.
+ */
+export const applyKeyboardFocus = async (page: E2EPage, locator: Locator) => {
+ await expect(async () => {
+ await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur());
+ await page.keyboard.press('Shift');
+ await locator.focus();
+ await expect(locator).toHaveClass(/ion-focused/, { timeout: 250 });
+ }).toPass({ timeout: 5000 });
+};
diff --git a/core/src/utils/test/playwright/index.ts b/core/src/utils/test/playwright/index.ts
index ce51b371266..777d35d4816 100644
--- a/core/src/utils/test/playwright/index.ts
+++ b/core/src/utils/test/playwright/index.ts
@@ -3,6 +3,7 @@ export * from './playwright-declarations';
export * from './page/event-spy';
export * from './page/utils';
export * from './drag-element';
+export * from './apply-keyboard-focus';
export * from './matchers';
export * from './viewports';
export * from './generator';