From 8723c9408b75c5c8b838140f2817fa63a2fbb65f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 May 2026 08:45:59 +0000
Subject: [PATCH 1/5] Add auto tooltip for button and badge
Agent-Logs-Url: https://github.com/vaadin/web-components/sessions/b85857dc-8cb9-4aaa-9268-da95a72b8b1e
---
packages/badge/package.json | 1 +
packages/badge/src/vaadin-badge.d.ts | 5 ++
packages/badge/src/vaadin-badge.js | 71 +++++++++++++++++++++++++-
packages/badge/test/badge.test.ts | 20 ++++++++
packages/button/package.json | 1 +
packages/button/src/vaadin-button.d.ts | 5 ++
packages/button/src/vaadin-button.js | 67 ++++++++++++++++++++++--
packages/button/test/button.test.ts | 20 ++++++++
8 files changed, 184 insertions(+), 6 deletions(-)
diff --git a/packages/badge/package.json b/packages/badge/package.json
index a6d988d039b..c1215790ab0 100644
--- a/packages/badge/package.json
+++ b/packages/badge/package.json
@@ -36,6 +36,7 @@
"dependencies": {
"@vaadin/a11y-base": "25.2.0-alpha12",
"@vaadin/component-base": "25.2.0-alpha12",
+ "@vaadin/tooltip": "25.2.0-alpha12",
"@vaadin/vaadin-themable-mixin": "25.2.0-alpha12",
"lit": "^3.0.0"
},
diff --git a/packages/badge/src/vaadin-badge.d.ts b/packages/badge/src/vaadin-badge.d.ts
index d3462f32e44..242eeebcd66 100644
--- a/packages/badge/src/vaadin-badge.d.ts
+++ b/packages/badge/src/vaadin-badge.d.ts
@@ -57,6 +57,11 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
*/
declare class Badge extends ElementMixin(ThemableMixin(HTMLElement)) {
+ /**
+ * When enabled, hides the content visually and shows it in a tooltip.
+ */
+ autoTooltip: boolean;
+
/**
* The number to display in the badge.
*/
diff --git a/packages/badge/src/vaadin-badge.js b/packages/badge/src/vaadin-badge.js
index ee174f1c616..a7f44941084 100644
--- a/packages/badge/src/vaadin-badge.js
+++ b/packages/badge/src/vaadin-badge.js
@@ -3,6 +3,7 @@
* Copyright (c) 2026 - 2026 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
+import '@vaadin/tooltip/src/vaadin-tooltip.js';
import { html, LitElement } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { screenReaderOnly } from '@vaadin/a11y-base/src/styles/sr-only-styles.js';
@@ -11,6 +12,7 @@ import { isEmptyTextNode } from '@vaadin/component-base/src/dom-utils.js';
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
import { SlotObserver } from '@vaadin/component-base/src/slot-observer.js';
+import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { badgeStyles } from './styles/vaadin-badge-base-styles.js';
@@ -93,6 +95,16 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L
number: {
type: Number,
},
+
+ /**
+ * When enabled, hides the content visually and shows it in a tooltip.
+ */
+ autoTooltip: {
+ type: Boolean,
+ value: false,
+ reflectToAttribute: true,
+ sync: true,
+ },
};
}
@@ -108,9 +120,10 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L
${this.number}
-
-
+
+
+
`;
}
@@ -123,6 +136,24 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L
}
}
+ /** @protected */
+ updated(props) {
+ super.updated(props);
+
+ if (props.has('autoTooltip')) {
+ this.__updateAutoTooltip();
+ }
+ }
+
+ /** @protected */
+ ready() {
+ super.ready();
+
+ this._tooltipController = new TooltipController(this);
+ this.addController(this._tooltipController);
+ this.__updateAutoTooltip();
+ }
+
/** @protected */
firstUpdated() {
super.firstUpdated();
@@ -137,6 +168,42 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L
this.toggleAttribute('has-icon', currentNodes.length > 0);
});
}
+
+ /** @private */
+ __getContentText() {
+ const slot = this.shadowRoot.querySelector('slot:not([name])');
+ return slot
+ .assignedNodes({ flatten: true })
+ .map((node) => node.textContent)
+ .join('')
+ .trim();
+ }
+
+ /** @private */
+ __hasCustomTooltip() {
+ return Array.from(this.children).some((node) => node !== this.__autoTooltip && node.slot === 'tooltip');
+ }
+
+ /** @private */
+ __updateAutoTooltip() {
+ const text = this.__getContentText();
+
+ if (!this.autoTooltip || !text || this.__hasCustomTooltip()) {
+ this.__autoTooltip?.remove();
+ return;
+ }
+
+ if (!this.__autoTooltip) {
+ this.__autoTooltip = document.createElement('vaadin-tooltip');
+ this.__autoTooltip.setAttribute('slot', 'tooltip');
+ }
+
+ this.__autoTooltip.setAttribute('text', text);
+
+ if (!this.__autoTooltip.isConnected) {
+ this.appendChild(this.__autoTooltip);
+ }
+ }
}
defineCustomElement(Badge);
diff --git a/packages/badge/test/badge.test.ts b/packages/badge/test/badge.test.ts
index 21e3f4f84ec..c441476dea1 100644
--- a/packages/badge/test/badge.test.ts
+++ b/packages/badge/test/badge.test.ts
@@ -116,4 +116,24 @@ describe('vaadin-badge', () => {
expect(badge.hasAttribute('has-icon')).to.be.false;
});
});
+
+ describe('auto-tooltip', () => {
+ beforeEach(async () => {
+ badge = fixtureSync('
New');
+ await nextRender();
+ });
+
+ it('should hide the content visually', () => {
+ const content = badge.shadowRoot!.querySelector('[part="content"]')!;
+
+ expect(content.classList.contains('sr-only')).to.be.true;
+ });
+
+ it('should add a tooltip with content text', () => {
+ const tooltip = badge.querySelector('vaadin-tooltip[slot="tooltip"]')!;
+
+ expect(tooltip).to.be.ok;
+ expect(tooltip.getAttribute('text')).to.equal('New');
+ });
+ });
});
diff --git a/packages/button/package.json b/packages/button/package.json
index 9cf7770b79e..487a9c7098d 100644
--- a/packages/button/package.json
+++ b/packages/button/package.json
@@ -37,6 +37,7 @@
"@open-wc/dedupe-mixin": "^1.3.0",
"@vaadin/a11y-base": "25.2.0-alpha12",
"@vaadin/component-base": "25.2.0-alpha12",
+ "@vaadin/tooltip": "25.2.0-alpha12",
"@vaadin/vaadin-themable-mixin": "25.2.0-alpha12",
"lit": "^3.0.0"
},
diff --git a/packages/button/src/vaadin-button.d.ts b/packages/button/src/vaadin-button.d.ts
index 530c9bbbd43..ad8136b1233 100644
--- a/packages/button/src/vaadin-button.d.ts
+++ b/packages/button/src/vaadin-button.d.ts
@@ -54,6 +54,11 @@ import { ButtonMixin } from './vaadin-button-mixin.js';
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
*/
declare class Button extends ButtonMixin(ElementMixin(ThemableMixin(HTMLElement))) {
+ /**
+ * When enabled, hides the label visually and shows it in a tooltip.
+ */
+ autoTooltip: boolean;
+
/**
* When disabled, the button is rendered as "dimmed".
*
diff --git a/packages/button/src/vaadin-button.js b/packages/button/src/vaadin-button.js
index d50336e7545..2efcb7e923e 100644
--- a/packages/button/src/vaadin-button.js
+++ b/packages/button/src/vaadin-button.js
@@ -3,7 +3,10 @@
* Copyright (c) 2017 - 2026 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
+import '@vaadin/tooltip/src/vaadin-tooltip.js';
import { html, LitElement } from 'lit';
+import { classMap } from 'lit/directives/class-map.js';
+import { screenReaderOnly } from '@vaadin/a11y-base/src/styles/sr-only-styles.js';
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
@@ -68,7 +71,7 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInj
}
static get styles() {
- return buttonStyles;
+ return [buttonStyles, screenReaderOnly];
}
static get properties() {
@@ -95,6 +98,16 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInj
reflectToAttribute: true,
sync: true,
},
+
+ /**
+ * When enabled, hides the label visually and shows it in a tooltip.
+ */
+ autoTooltip: {
+ type: Boolean,
+ value: false,
+ reflectToAttribute: true,
+ sync: true,
+ },
};
}
@@ -105,30 +118,76 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInj
-
-
+
+
-
+
`;
}
+ /** @protected */
+ updated(props) {
+ super.updated(props);
+
+ if (props.has('autoTooltip')) {
+ this.__updateAutoTooltip();
+ }
+ }
+
/** @protected */
ready() {
super.ready();
this._tooltipController = new TooltipController(this);
this.addController(this._tooltipController);
+ this.__updateAutoTooltip();
}
/** @override */
__shouldAllowFocusWhenDisabled() {
return window.Vaadin.featureFlags.accessibleDisabledButtons;
}
+
+ /** @private */
+ __getLabelText() {
+ const slot = this.shadowRoot.querySelector('slot:not([name])');
+ return slot
+ .assignedNodes({ flatten: true })
+ .map((node) => node.textContent)
+ .join('')
+ .trim();
+ }
+
+ /** @private */
+ __hasCustomTooltip() {
+ return Array.from(this.children).some((node) => node !== this.__autoTooltip && node.slot === 'tooltip');
+ }
+
+ /** @private */
+ __updateAutoTooltip() {
+ const text = this.__getLabelText();
+
+ if (!this.autoTooltip || !text || this.__hasCustomTooltip()) {
+ this.__autoTooltip?.remove();
+ return;
+ }
+
+ if (!this.__autoTooltip) {
+ this.__autoTooltip = document.createElement('vaadin-tooltip');
+ this.__autoTooltip.setAttribute('slot', 'tooltip');
+ }
+
+ this.__autoTooltip.setAttribute('text', text);
+
+ if (!this.__autoTooltip.isConnected) {
+ this.appendChild(this.__autoTooltip);
+ }
+ }
}
defineCustomElement(Button);
diff --git a/packages/button/test/button.test.ts b/packages/button/test/button.test.ts
index 58ce2f0e747..b85dc79801e 100644
--- a/packages/button/test/button.test.ts
+++ b/packages/button/test/button.test.ts
@@ -206,4 +206,24 @@ describe('vaadin-button', () => {
expect(document.activeElement).to.equal(lastGlobalFocusable);
});
});
+
+ describe('auto-tooltip', () => {
+ beforeEach(async () => {
+ button = fixtureSync('Press me');
+ await nextRender();
+ });
+
+ it('should hide the label visually', () => {
+ const label = button.shadowRoot!.querySelector('[part="label"]')!;
+
+ expect(label.classList.contains('sr-only')).to.be.true;
+ });
+
+ it('should add a tooltip with label text', () => {
+ const tooltip = button.querySelector('vaadin-tooltip[slot="tooltip"]')!;
+
+ expect(tooltip).to.be.ok;
+ expect(tooltip.getAttribute('text')).to.equal('Press me');
+ });
+ });
});
From ea4f927e0530bd94cbd5153e4dd571c0a6bfc252 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 May 2026 08:47:34 +0000
Subject: [PATCH 2/5] Document badge tooltip slot
Agent-Logs-Url: https://github.com/vaadin/web-components/sessions/b85857dc-8cb9-4aaa-9268-da95a72b8b1e
---
packages/badge/src/vaadin-badge.d.ts | 2 ++
packages/badge/src/vaadin-badge.js | 2 ++
2 files changed, 4 insertions(+)
diff --git a/packages/badge/src/vaadin-badge.d.ts b/packages/badge/src/vaadin-badge.d.ts
index 242eeebcd66..463933afbdd 100644
--- a/packages/badge/src/vaadin-badge.d.ts
+++ b/packages/badge/src/vaadin-badge.d.ts
@@ -19,6 +19,7 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
* ---------|-------------
* (none) | Default slot for the badge text content
* `icon` | Slot for an icon to place before the text
+ * `tooltip` | Slot for a tooltip
*
* ### Styling
*
@@ -37,6 +38,7 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
* `has-icon` | Set when the badge has content in the icon slot
* `has-content` | Set when the badge has content in the default slot
* `has-number` | Set when the badge has a number value
+ * `has-tooltip` | Set when the badge has a slotted tooltip
*
* The following custom CSS properties are available for styling:
*
diff --git a/packages/badge/src/vaadin-badge.js b/packages/badge/src/vaadin-badge.js
index a7f44941084..ffc7fe45a07 100644
--- a/packages/badge/src/vaadin-badge.js
+++ b/packages/badge/src/vaadin-badge.js
@@ -30,6 +30,7 @@ import { badgeStyles } from './styles/vaadin-badge-base-styles.js';
* ---------|-------------
* (none) | Default slot for the badge text content
* `icon` | Slot for an icon to place before the text
+ * `tooltip` | Slot for a tooltip
*
* ### Styling
*
@@ -48,6 +49,7 @@ import { badgeStyles } from './styles/vaadin-badge-base-styles.js';
* `has-icon` | Set when the badge has content in the icon slot
* `has-content` | Set when the badge has content in the default slot
* `has-number` | Set when the badge has a number value
+ * `has-tooltip` | Set when the badge has a slotted tooltip
*
* The following custom CSS properties are available for styling:
*
From 052128aa740918b2417e57a6519f83fc8271df95 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 May 2026 08:51:16 +0000
Subject: [PATCH 3/5] Guard auto tooltip slot lookup
Agent-Logs-Url: https://github.com/vaadin/web-components/sessions/b85857dc-8cb9-4aaa-9268-da95a72b8b1e
---
packages/badge/src/vaadin-badge.js | 10 ++++++----
packages/button/src/vaadin-button.js | 10 ++++++----
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/packages/badge/src/vaadin-badge.js b/packages/badge/src/vaadin-badge.js
index ffc7fe45a07..7b0da0531a7 100644
--- a/packages/badge/src/vaadin-badge.js
+++ b/packages/badge/src/vaadin-badge.js
@@ -175,10 +175,12 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L
__getContentText() {
const slot = this.shadowRoot.querySelector('slot:not([name])');
return slot
- .assignedNodes({ flatten: true })
- .map((node) => node.textContent)
- .join('')
- .trim();
+ ? slot
+ .assignedNodes({ flatten: true })
+ .map((node) => node.textContent)
+ .join('')
+ .trim()
+ : '';
}
/** @private */
diff --git a/packages/button/src/vaadin-button.js b/packages/button/src/vaadin-button.js
index 2efcb7e923e..225affe7c50 100644
--- a/packages/button/src/vaadin-button.js
+++ b/packages/button/src/vaadin-button.js
@@ -157,10 +157,12 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInj
__getLabelText() {
const slot = this.shadowRoot.querySelector('slot:not([name])');
return slot
- .assignedNodes({ flatten: true })
- .map((node) => node.textContent)
- .join('')
- .trim();
+ ? slot
+ .assignedNodes({ flatten: true })
+ .map((node) => node.textContent)
+ .join('')
+ .trim()
+ : '';
}
/** @private */
From caf0654e3e604b27ec3af9dd3c90e93e7434a0a0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 May 2026 10:35:12 +0000
Subject: [PATCH 4/5] Update badge DOM snapshots for tooltip slot
Agent-Logs-Url: https://github.com/vaadin/web-components/sessions/868fcc0e-2dac-4420-a00e-f006f272b001
---
.../badge/test/dom/__snapshots__/badge.test.snap.js | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/packages/badge/test/dom/__snapshots__/badge.test.snap.js b/packages/badge/test/dom/__snapshots__/badge.test.snap.js
index 4c501ce9fdb..54c29388318 100644
--- a/packages/badge/test/dom/__snapshots__/badge.test.snap.js
+++ b/packages/badge/test/dom/__snapshots__/badge.test.snap.js
@@ -31,6 +31,8 @@ snapshots["vaadin-badge shadow default"] =
+
+
`;
/* end snapshot vaadin-badge shadow default */
@@ -46,6 +48,8 @@ snapshots["vaadin-badge shadow number"] =
+
+
`;
/* end snapshot vaadin-badge shadow number */
@@ -69,6 +73,8 @@ snapshots["vaadin-badge shadow dot"] =
+
+
`;
/* end snapshot vaadin-badge shadow dot */
@@ -89,6 +95,8 @@ snapshots["vaadin-badge shadow icon-only"] =
+
+
`;
/* end snapshot vaadin-badge shadow icon-only */
@@ -109,6 +117,8 @@ snapshots["vaadin-badge shadow number-only"] =
+
+
`;
/* end snapshot vaadin-badge shadow number-only */
From 660900858476335d8303ed54f3dee7ef0864ceb1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 May 2026 10:57:48 +0000
Subject: [PATCH 5/5] Prevent auto tooltips from setting aria-describedby
Agent-Logs-Url: https://github.com/vaadin/web-components/sessions/73002448-037c-4106-b8ff-668b11b757a5
---
packages/badge/src/vaadin-badge.js | 1 +
packages/badge/test/badge.test.ts | 9 +++++++++
packages/button/src/vaadin-button.js | 1 +
packages/button/test/button.test.ts | 9 +++++++++
4 files changed, 20 insertions(+)
diff --git a/packages/badge/src/vaadin-badge.js b/packages/badge/src/vaadin-badge.js
index 7b0da0531a7..cd812d325e3 100644
--- a/packages/badge/src/vaadin-badge.js
+++ b/packages/badge/src/vaadin-badge.js
@@ -200,6 +200,7 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L
if (!this.__autoTooltip) {
this.__autoTooltip = document.createElement('vaadin-tooltip');
this.__autoTooltip.setAttribute('slot', 'tooltip');
+ this.__autoTooltip.ariaTarget = null;
}
this.__autoTooltip.setAttribute('text', text);
diff --git a/packages/badge/test/badge.test.ts b/packages/badge/test/badge.test.ts
index c441476dea1..20a59b464f4 100644
--- a/packages/badge/test/badge.test.ts
+++ b/packages/badge/test/badge.test.ts
@@ -135,5 +135,14 @@ describe('vaadin-badge', () => {
expect(tooltip).to.be.ok;
expect(tooltip.getAttribute('text')).to.equal('New');
});
+
+ it('should not set aria-describedby', async () => {
+ const tooltip = badge.querySelector('vaadin-tooltip[slot="tooltip"]')! as any;
+ await nextUpdate(tooltip);
+
+ expect(badge.hasAttribute('auto-tooltip')).to.be.true;
+ expect(tooltip.ariaTarget).to.equal(null);
+ expect(badge.hasAttribute('aria-describedby')).to.be.false;
+ });
});
});
diff --git a/packages/button/src/vaadin-button.js b/packages/button/src/vaadin-button.js
index 225affe7c50..23544e6637c 100644
--- a/packages/button/src/vaadin-button.js
+++ b/packages/button/src/vaadin-button.js
@@ -182,6 +182,7 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInj
if (!this.__autoTooltip) {
this.__autoTooltip = document.createElement('vaadin-tooltip');
this.__autoTooltip.setAttribute('slot', 'tooltip');
+ this.__autoTooltip.ariaTarget = null;
}
this.__autoTooltip.setAttribute('text', text);
diff --git a/packages/button/test/button.test.ts b/packages/button/test/button.test.ts
index b85dc79801e..3e90b3f5587 100644
--- a/packages/button/test/button.test.ts
+++ b/packages/button/test/button.test.ts
@@ -225,5 +225,14 @@ describe('vaadin-button', () => {
expect(tooltip).to.be.ok;
expect(tooltip.getAttribute('text')).to.equal('Press me');
});
+
+ it('should not set aria-describedby', async () => {
+ const tooltip = button.querySelector('vaadin-tooltip[slot="tooltip"]')! as any;
+ await nextUpdate(tooltip);
+
+ expect(button.hasAttribute('auto-tooltip')).to.be.true;
+ expect(tooltip.ariaTarget).to.equal(null);
+ expect(button.hasAttribute('aria-describedby')).to.be.false;
+ });
});
});