diff --git a/packages/main/cypress/specs/Input.cy.tsx b/packages/main/cypress/specs/Input.cy.tsx
index 4924b72911157..247421e2a0d53 100644
--- a/packages/main/cypress/specs/Input.cy.tsx
+++ b/packages/main/cypress/specs/Input.cy.tsx
@@ -3624,16 +3624,21 @@ describe("Input built-in filtering", () => {
.should("exist");
});
- it("should sync value-state to slotted input icons", () => {
+ it("should forward value-state styling to slotted input icons via inherited CSS custom properties", () => {
cy.mount(
);
+ // The parent Input publishes --_ui5_input_icon_state_padding on its host when value-state is set.
+ // CSS custom properties inherit through the shadow boundary, so the slotted icon reads it inside its own shadow root.
cy.get("[ui5-input]")
.find("[ui5-input-icon]")
- .should("have.attr", "value-state", "Negative");
+ .then($icon => {
+ const computedPadding = getComputedStyle($icon[0]).getPropertyValue("--_ui5_input_icon_state_padding").trim();
+ expect(computedPadding).to.not.equal("");
+ });
});
it("should work with multiple icons and clear icon", () => {
@@ -3670,5 +3675,44 @@ describe("Input built-in filtering", () => {
cy.get("@iconClick")
.should("have.been.calledOnce");
});
+
+ it("should forward disabled state to slotted input icons via inherited CSS custom properties", () => {
+ cy.mount(
+
+
+
+ );
+
+ // The parent Input publishes --_ui5_input_icon_state_pointer_events: none on :host([disabled]).
+ // That inherits into the icon's shadow root and natively blocks hover/active/click.
+ cy.get("[ui5-input]")
+ .find("[ui5-input-icon]")
+ .then($icon => {
+ const styles = getComputedStyle($icon[0]);
+ expect(styles.getPropertyValue("--_ui5_input_icon_state_pointer_events").trim()).to.equal("none");
+ expect(styles.getPropertyValue("--_ui5_input_icon_state_cursor").trim()).to.equal("default");
+ // Opacity inherited as a custom prop, and the icon's shadow root applies it via opacity: var(...)
+ expect(styles.getPropertyValue("--_ui5_input_icon_state_opacity").trim()).to.not.equal("");
+ });
+ });
+
+ it("should forward readonly state to slotted input icons via inherited CSS custom properties", () => {
+ cy.mount(
+
+
+
+ );
+
+ // Readonly parent publishes pointer-events: none + cursor: default (no opacity change).
+ cy.get("[ui5-input]")
+ .find("[ui5-input-icon]")
+ .then($icon => {
+ const styles = getComputedStyle($icon[0]);
+ expect(styles.getPropertyValue("--_ui5_input_icon_state_pointer_events").trim()).to.equal("none");
+ expect(styles.getPropertyValue("--_ui5_input_icon_state_cursor").trim()).to.equal("default");
+ // Readonly does not dim — opacity var should remain unset.
+ expect(styles.getPropertyValue("--_ui5_input_icon_state_opacity").trim()).to.equal("");
+ });
+ });
});
});
diff --git a/packages/main/src/Input.ts b/packages/main/src/Input.ts
index b7bac5a95cae3..9379a44bc5a4d 100644
--- a/packages/main/src/Input.ts
+++ b/packages/main/src/Input.ts
@@ -770,15 +770,6 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
this._effectiveShowClearIcon = (this.showClearIcon && !!this.value && !this.readonly && !this.disabled);
this.style.setProperty("--_ui5-input-icons-count", `${this.iconsCount}`);
- // Sync value state to slotted input icons
- this.icon.forEach((iconElement: IIcon) => {
- if (iconElement.hasAttribute("ui5-input-icon")) {
- (iconElement as any).valueState = this.valueState;
- (iconElement as any).readonly = this.readonly;
- (iconElement as any)._parentDisabled = this.disabled;
- }
- });
-
const hasItems = !!this._flattenItems.length;
const hasValue = !!this.value;
const isFocused = this.shadowRoot!.querySelector("input") === getActiveElement();
diff --git a/packages/main/src/InputIcon.ts b/packages/main/src/InputIcon.ts
index 9340285aac73e..9bb98166af19a 100644
--- a/packages/main/src/InputIcon.ts
+++ b/packages/main/src/InputIcon.ts
@@ -3,7 +3,6 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
-import type ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js";
import InputIconTemplate from "./InputIconTemplate.js";
import inputIconCss from "./generated/themes/InputIcon.css.js";
@@ -63,6 +62,7 @@ class InputIcon extends UI5Element {
* Defines the accessible name of the icon.
*
* **Note:** This property is used for accessibility purposes and will be announced by screen readers.
+ * When set, it is also rendered as a native `title` tooltip.
*
* @default undefined
* @public
@@ -70,50 +70,6 @@ class InputIcon extends UI5Element {
@property()
accessibleName?: string;
- /**
- * Defines whether the tooltip is shown.
- *
- * **Note:** The tooltip text should be provided via the `accessible-name` property.
- *
- * @default false
- * @public
- */
- @property({ type: Boolean })
- showTooltip = false;
-
- /**
- * Defines the value state of the icon.
- *
- * **Note:** This property should match the parent input's value state for consistent styling.
- *
- * @default "None"
- * @public
- */
- @property()
- valueState: `${ValueState}` = "None";
-
- /**
- * Defines whether the icon is disabled.
- *
- * **Note:** Disabled icons are not interactive and do not fire click events.
- *
- * @default false
- * @public
- */
- @property({ type: Boolean })
- disabled = false;
-
- /**
- * Defines whether the icon is readonly.
- *
- * **Note:** Readonly icons are not interactive and do not fire click events.
- *
- * @default false
- * @public
- */
- @property({ type: Boolean })
- readonly = false;
-
/**
* @private
*/
@@ -126,27 +82,13 @@ class InputIcon extends UI5Element {
@property({ type: Boolean, noAttribute: true })
_focused = false;
- /**
- * @private
- */
- @property({ type: Boolean, noAttribute: true })
- _parentDisabled = false;
-
_onclick(e: MouseEvent) {
- if (this.disabled || this.readonly || this._parentDisabled) {
- e.stopImmediatePropagation();
- e.preventDefault();
- return;
- }
-
e.stopImmediatePropagation();
this.fireDecoratorEvent("click");
}
_onmousedown() {
- if (!this.disabled && !this.readonly && !this._parentDisabled) {
- this._pressed = true;
- }
+ this._pressed = true;
}
_onmouseup() {
@@ -167,10 +109,6 @@ class InputIcon extends UI5Element {
}
_onkeydown(e: KeyboardEvent) {
- if (this.disabled || this.readonly || this._parentDisabled) {
- return;
- }
-
if (isEnter(e) || isSpace(e)) {
this._pressed = true;
e.preventDefault(); // Prevent scrolling on Space
@@ -178,26 +116,18 @@ class InputIcon extends UI5Element {
}
_onkeyup(e: KeyboardEvent) {
- if (this.disabled || this.readonly || this._parentDisabled) {
- return;
- }
-
if (isEnter(e) || isSpace(e)) {
this._pressed = false;
this.fireDecoratorEvent("click");
}
}
- get effectiveTabIndex() {
- return (this.disabled || this.readonly || this._parentDisabled) ? -1 : 0;
- }
-
get effectiveAriaLabel() {
return this.accessibleName || undefined;
}
get effectiveTitle() {
- return this.showTooltip && this.accessibleName ? this.accessibleName : undefined;
+ return this.accessibleName || undefined;
}
}
diff --git a/packages/main/src/InputIconTemplate.tsx b/packages/main/src/InputIconTemplate.tsx
index 249fcce58cc7a..20dbd20d812d3 100644
--- a/packages/main/src/InputIconTemplate.tsx
+++ b/packages/main/src/InputIconTemplate.tsx
@@ -3,40 +3,34 @@ import Icon from "./Icon.js";
export default function InputIconTemplate(this: InputIcon) {
return (
- <>
- {!this.readonly &&
-
- {this.name && (
-
- )}
-
- }
- >
+
+ {this.name && (
+
+ )}
+
);
}
diff --git a/packages/main/src/themes/Input.css b/packages/main/src/themes/Input.css
index 1e4b79a63087b..c1b61ca559788 100644
--- a/packages/main/src/themes/Input.css
+++ b/packages/main/src/themes/Input.css
@@ -517,4 +517,64 @@
[ui5-responsive-popover] [ui5-input] {
width: 100%;
+}
+
+/*
+ * Value-state forwarding for slotted .
+ *
+ * The parent Input publishes state-specific values via CSS custom properties on its host.
+ * CSS custom properties inherit through the shadow boundary, so a slotted
+ * automatically picks them up inside its own shadow root — no JS DOM mutation needed.
+ */
+:host([value-state="Negative"]),
+:host([value-state="Critical"]) {
+ --_ui5_input_icon_state_padding: var(--_ui5_input_error_warning_icon_padding);
+}
+
+:host([value-state="Information"]) {
+ --_ui5_input_icon_state_padding: var(--_ui5_input_information_icon_padding);
+}
+
+:host([value-state="Negative"]) {
+ --_ui5_input_icon_state_pressed_shadow: var(--_ui5_input_error_icon_box_shadow);
+ --_ui5_input_icon_state_pressed_color: var(--_ui5_input_icon_error_pressed_color);
+ --_ui5_input_icon_state_focus_outline_color: var(--_ui5_input_focused_value_state_error_focus_outline_color);
+}
+
+:host([value-state="Critical"]) {
+ --_ui5_input_icon_state_pressed_shadow: var(--_ui5_input_warning_icon_box_shadow);
+ --_ui5_input_icon_state_pressed_color: var(--_ui5_input_icon_warning_pressed_color);
+ --_ui5_input_icon_state_focus_outline_color: var(--_ui5_input_focused_value_state_warning_focus_outline_color);
+}
+
+:host([value-state="Information"]) {
+ --_ui5_input_icon_state_pressed_shadow: var(--_ui5_input_information_icon_box_shadow);
+ --_ui5_input_icon_state_pressed_color: var(--_ui5_input_icon_information_pressed_color);
+ --_ui5_input_icon_state_focus_outline_color: var(--sapContent_FocusColor);
+}
+
+:host([value-state="Positive"]) {
+ --_ui5_input_icon_state_pressed_shadow: var(--_ui5_input_success_icon_box_shadow);
+ --_ui5_input_icon_state_pressed_color: var(--_ui5_input_icon_success_pressed_color);
+ --_ui5_input_icon_state_focus_outline_color: var(--_ui5_input_focused_value_state_success_focus_outline_color);
+}
+
+/*
+ * Disabled forwarding for slotted . Same mechanism: parent publishes vars,
+ * icon reads them via var() with defaults, no JS DOM mutation.
+ */
+:host([disabled]) {
+ --_ui5_input_icon_state_opacity: var(--sapContent_DisabledOpacity);
+ --_ui5_input_icon_state_pointer_events: none;
+ --_ui5_input_icon_state_cursor: default;
+}
+
+/*
+ * Readonly forwarding: the input is still readable but non-editable, so slotted interactive
+ * icons (search, voice, camera, …) should not fire either. No opacity change — the input
+ * itself does not dim in readonly.
+ */
+:host([readonly]:not([disabled])) {
+ --_ui5_input_icon_state_pointer_events: none;
+ --_ui5_input_icon_state_cursor: default;
}
\ No newline at end of file
diff --git a/packages/main/src/themes/InputIcon.css b/packages/main/src/themes/InputIcon.css
index 876b59e176645..08319972bd94e 100644
--- a/packages/main/src/themes/InputIcon.css
+++ b/packages/main/src/themes/InputIcon.css
@@ -5,9 +5,14 @@
.inputIcon {
color: var(--_ui5_input_icon_color);
- cursor: pointer;
+ /* cursor / opacity / pointer-events are state-aware — parent Input publishes overrides on :host([disabled]).
+ Defaults preserve the plain interactive appearance. */
+ cursor: var(--_ui5_input_icon_state_cursor, pointer);
+ opacity: var(--_ui5_input_icon_state_opacity, 1);
+ pointer-events: var(--_ui5_input_icon_state_pointer_events, auto);
outline: none;
- padding: var(--_ui5_input_icon_padding);
+ /* padding is state-aware; parent Input publishes the state-specific value via CSS custom property */
+ padding: var(--_ui5_input_icon_state_padding, var(--_ui5_input_icon_padding));
border-inline-start: var(--_ui5_input_icon_border);
min-width: 1rem;
min-height: 1rem;
@@ -18,38 +23,33 @@
}
.inputIcon:focus-visible {
- outline: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--sapContent_FocusColor);
+ outline: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--_ui5_input_icon_state_focus_outline_color, var(--sapContent_FocusColor));
outline-offset: -0.125rem;
}
.inputIcon.inputIcon--pressed {
background: var(--_ui5_input_icon_pressed_bg);
- box-shadow: var(--_ui5_input_icon_box_shadow);
+ /* box-shadow / color fall back to the plain variant when no value state is set */
+ box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow));
border-inline-start: var(--_ui5_select_hover_icon_left_border);
- color: var(--_ui5_input_icon_pressed_color);
+ color: var(--_ui5_input_icon_state_pressed_color, var(--_ui5_input_icon_pressed_color));
}
-.inputIcon:not(.inputIcon--disabled):active {
+.inputIcon:active {
background-color: var(--sapButton_Active_Background);
- box-shadow: var(--_ui5_input_icon_box_shadow);
+ box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow));
border-inline-start: var(--_ui5_select_hover_icon_left_border);
- color: var(--_ui5_input_icon_pressed_color);
+ color: var(--_ui5_input_icon_state_pressed_color, var(--_ui5_input_icon_pressed_color));
}
-.inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover {
+.inputIcon:not(.inputIcon--pressed):not(:active):hover {
background: var(--_ui5_input_icon_hover_bg);
- box-shadow: var(--_ui5_input_icon_box_shadow);
+ box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow));
}
-.inputIcon:not(.inputIcon--disabled):hover {
+.inputIcon:hover {
border-inline-start: var(--_ui5_select_hover_icon_left_border);
- box-shadow: var(--_ui5_input_icon_box_shadow);
-}
-
-/* Disabled state styling */
-.inputIcon.inputIcon--disabled {
- opacity: var(--sapContent_DisabledOpacity);
- cursor: default;
+ box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow));
}
@@ -63,73 +63,8 @@
flex-shrink: 0;
}
-/* Value state styling */
-:host([value-state="Negative"]) .inputIcon,
-:host([value-state="Critical"]) .inputIcon {
- padding: var(--_ui5_input_error_warning_icon_padding);
-}
-
-:host([value-state="Information"]) .inputIcon {
- padding: var(--_ui5_input_information_icon_padding);
-}
-
-:host([value-state="Negative"]) .inputIcon:not(.inputIcon--disabled):active,
-:host([value-state="Negative"]) .inputIcon.inputIcon--pressed {
- box-shadow: var(--_ui5_input_error_icon_box_shadow);
- color: var(--_ui5_input_icon_error_pressed_color);
-}
-
-:host([value-state="Negative"]) .inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover {
- box-shadow: var(--_ui5_input_error_icon_box_shadow);
-}
-
-:host([value-state="Critical"]) .inputIcon:not(.inputIcon--disabled):active,
-:host([value-state="Critical"]) .inputIcon.inputIcon--pressed {
- box-shadow: var(--_ui5_input_warning_icon_box_shadow);
- color: var(--_ui5_input_icon_warning_pressed_color);
-}
-
-:host([value-state="Critical"]) .inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover {
- box-shadow: var(--_ui5_input_warning_icon_box_shadow);
-}
-
-:host([value-state="Information"]) .inputIcon:not(.inputIcon--disabled):active,
-:host([value-state="Information"]) .inputIcon.inputIcon--pressed {
- box-shadow: var(--_ui5_input_information_icon_box_shadow);
- color: var(--_ui5_input_icon_information_pressed_color);
-}
-
-:host([value-state="Information"]) .inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover {
- box-shadow: var(--_ui5_input_information_icon_box_shadow);
-}
-
-:host([value-state="Positive"]) .inputIcon:not(.inputIcon--disabled):active,
-:host([value-state="Positive"]) .inputIcon.inputIcon--pressed {
- box-shadow: var(--_ui5_input_success_icon_box_shadow);
- color: var(--_ui5_input_icon_success_pressed_color);
-}
-
-:host([value-state="Positive"]) .inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover {
- box-shadow: var(--_ui5_input_success_icon_box_shadow);
-}
-
-/* Override focus outline colors for value states */
-:host([value-state="Negative"]) .inputIcon.inputIcon--focused {
- outline-color: var(--_ui5_input_focused_value_state_error_focus_outline_color);
- border-inline-start-color: var(--_ui5_input_focused_value_state_error_focus_outline_color);
-}
-
-:host([value-state="Critical"]) .inputIcon.inputIcon--focused {
- outline-color: var(--_ui5_input_focused_value_state_warning_focus_outline_color);
- border-inline-start-color: var(--_ui5_input_focused_value_state_warning_focus_outline_color);
-}
-
-:host([value-state="Information"]) .inputIcon.inputIcon--focused {
- outline-color: var(--sapContent_FocusColor);
- border-inline-start-color: var(--sapContent_FocusColor);
-}
-
-:host([value-state="Positive"]) .inputIcon.inputIcon--focused {
- outline-color: var(--_ui5_input_focused_value_state_success_focus_outline_color);
- border-inline-start-color: var(--_ui5_input_focused_value_state_success_focus_outline_color);
+/* Focus-outline colour when the parent Input has a value state — driven by inherited CSS custom property */
+.inputIcon.inputIcon--focused {
+ outline-color: var(--_ui5_input_icon_state_focus_outline_color, var(--sapContent_FocusColor));
+ border-inline-start-color: var(--_ui5_input_icon_state_focus_outline_color);
}
diff --git a/packages/main/test/pages/InputInteractiveIcons.html b/packages/main/test/pages/InputInteractiveIcons.html
index c8d6c2e81006e..a29c9071a9cde 100644
--- a/packages/main/test/pages/InputInteractiveIcons.html
+++ b/packages/main/test/pages/InputInteractiveIcons.html
@@ -180,9 +180,6 @@ With InputIcon (Interactive)
-
- Toggle Icon Disabled
-
Negative state:
@@ -536,11 +533,7 @@ 9. Disabled Inputs with Interactive Icons
});
// Toggle disabled for a single icon
- const toggleIconDisabledBtn = document.getElementById("toggle-icon-disabled");
- const toggleIcon = document.getElementById("toggle-icon");
- toggleIconDisabledBtn.addEventListener("click", () => {
- toggleIcon.toggleAttribute("disabled");
- });
+ // (Removed: ui5-input-icon no longer exposes a `disabled` property.)
// Event logging for accessibility demo
const eventLog = document.getElementById("event-log-content");
diff --git a/packages/website/docs/_components_pages/main/Input/InputIcon.mdx b/packages/website/docs/_components_pages/main/Input/InputIcon.mdx
new file mode 100644
index 0000000000000..fb322caab9073
--- /dev/null
+++ b/packages/website/docs/_components_pages/main/Input/InputIcon.mdx
@@ -0,0 +1,7 @@
+---
+slug: ../../InputIcon
+---
+
+<%COMPONENT_OVERVIEW%>
+
+<%COMPONENT_METADATA%>