From 58f6de216b0759f4d35921c748f4773bde418596 Mon Sep 17 00:00:00 2001 From: kavyansh18 Date: Sat, 5 Sep 2026 19:39:52 +0530 Subject: [PATCH 1/2] fix(input): improve clear button focus contrast --- core/src/components/input/input.scss | 2 +- .../components/input/test/basic/input.e2e.ts | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index e24bf990eb9..1fac83cad3f 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -225,7 +225,7 @@ * being activated, so we never get to that state. */ .input-clear-icon:focus { - opacity: 0.5; + opacity: 1; } :host(.has-value) .input-clear-icon { diff --git a/core/src/components/input/test/basic/input.e2e.ts b/core/src/components/input/test/basic/input.e2e.ts index 65a921a0f6f..381522873ae 100644 --- a/core/src/components/input/test/basic/input.e2e.ts +++ b/core/src/components/input/test/basic/input.e2e.ts @@ -129,6 +129,27 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c const item = page.locator('ion-item'); await expect(item).toHaveScreenshot(screenshot(`input-with-clear-button-item-color`)); }); + + test('should retain opacity: 1 when the clear button receives keyboard focus', async ({ page, browserName }) => { + const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab'; + + await page.setContent( + ` + + `, + config + ); + + const input = page.locator('ion-input'); + const nativeInput = input.locator('input'); + const clearButton = input.locator('.input-clear-icon'); + + await nativeInput.focus(); + await page.keyboard.press(tabKey); + + await expect(clearButton).toBeFocused(); + await expect(clearButton).toHaveCSS('opacity', '1'); + }); }); test.describe(title('input: click'), () => { From 6ce1911bc414ef02f3d2e95140626b5695f00480 Mon Sep 17 00:00:00 2001 From: kavyansh18 Date: Wed, 21 Oct 2026 23:10:36 +0530 Subject: [PATCH 2/2] fix(input): improve clear button focus contrast --- core/api.txt | 1 + core/src/components.d.ts | 11 ++++++++ core/src/components/input/input.scss | 9 ++++++ core/src/components/input/input.tsx | 9 ++++-- .../components/input/test/basic/input.e2e.ts | 28 ++++++++++++++++++- .../angular/src/lazy/directives/proxies.ts | 4 +-- packages/vue/src/proxies.ts | 1 + 7 files changed, 58 insertions(+), 5 deletions(-) diff --git a/core/api.txt b/core/api.txt index 5a79c3903aa..7f82ae910a1 100644 --- a/core/api.txt +++ b/core/api.txt @@ -716,6 +716,7 @@ ion-input,prop,autocapitalize,string,'off',false,false ion-input,prop,autocomplete,"additional-name" | "address-level1" | "address-level2" | "address-level3" | "address-level4" | "address-line1" | "address-line2" | "address-line3" | "bday" | "bday-day" | "bday-month" | "bday-year" | "cc-additional-name" | "cc-csc" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-family-name" | "cc-given-name" | "cc-name" | "cc-number" | "cc-type" | "country" | "country-name" | "current-password" | "email" | "family-name" | "given-name" | "honorific-prefix" | "honorific-suffix" | "impp" | "language" | "name" | "new-password" | "nickname" | "off" | "on" | "one-time-code" | "organization" | "organization-title" | "photo" | "postal-code" | "sex" | "street-address" | "tel" | "tel-area-code" | "tel-country-code" | "tel-extension" | "tel-local" | "tel-national" | "transaction-amount" | "transaction-currency" | "url" | "username",'off',false,false ion-input,prop,autocorrect,boolean,false,false,false ion-input,prop,autofocus,boolean,false,false,false +ion-input,prop,clearButtonText,string,'reset',false,false ion-input,prop,clearInput,boolean,false,false,false ion-input,prop,clearInputIcon,string | undefined,undefined,false,false ion-input,prop,clearOnEdit,boolean | undefined,undefined,false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index a6c8a7032d3..f1555707f8b 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -1310,6 +1310,11 @@ export namespace Components { * @default false */ "autofocus": boolean; + /** + * The accessible label for the clear button. Only applies when `clearInput` is set to `true`. + * @default 'reset' + */ + "clearButtonText": string; /** * If `true`, a clear icon will appear in the input when there is a value. Clicking it clears the input. * @default false @@ -6462,6 +6467,11 @@ declare namespace LocalJSX { * @default false */ "autofocus"?: boolean; + /** + * The accessible label for the clear button. Only applies when `clearInput` is set to `true`. + * @default 'reset' + */ + "clearButtonText"?: string; /** * If `true`, a clear icon will appear in the input when there is a value. Clicking it clears the input. * @default false @@ -9205,6 +9215,7 @@ declare namespace LocalJSX { "autofocus": boolean; "clearInput": boolean; "clearInputIcon": string; + "clearButtonText": string; "clearOnEdit": boolean; "counter": boolean; "debounce": number; diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index 1fac83cad3f..ee9cc5f134c 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -228,6 +228,15 @@ opacity: 1; } +.input-clear-icon:focus-visible { + @include border-radius(4px); + + outline: 2px solid #000; + outline-offset: 1px; + + box-shadow: 0 0 0 1px #fff; +} + :host(.has-value) .input-clear-icon { visibility: visible; } diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index dbbff605692..a5d0935e3d2 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -131,6 +131,11 @@ export class Input implements ComponentInterface { */ @Prop() clearInputIcon?: string; + /** + * The accessible label for the clear button. Only applies when `clearInput` is set to `true`. + */ + @Prop() clearButtonText = 'reset'; + /** * If `true`, the value will be cleared after focus upon edit. Defaults to `true` when `type` is `"password"`, `false` for all other types. */ @@ -856,7 +861,7 @@ export class Input implements ComponentInterface { } render() { - const { disabled, fill, readonly, shape, inputId, labelPlacement, hasFocus, clearInputIcon } = this; + const { disabled, fill, readonly, shape, inputId, labelPlacement, hasFocus, clearInputIcon, clearButtonText } = this; const mode = getIonMode(this); const value = this.getValue(); const inItem = hostContext('ion-item', this.el); @@ -945,7 +950,7 @@ export class Input implements ComponentInterface {
{this.clearInput && !readonly && !disabled && (