feat(textfield, number-field): add tooltip for a11y on truncated input text#6038
feat(textfield, number-field): add tooltip for a11y on truncated input text#6038rise-erpelding wants to merge 20 commits intomainfrom
Conversation
🦋 Changeset detectedLatest commit: 425a701 The changes in this PR will be included in the next version bump. This PR includes changesets to release 83 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
a03db36 to
c46b5e3
Compare
📚 Branch Preview Links🔍 First Generation Visual Regression Test ResultsWhen a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:
Deployed to Azure Blob Storage: If the changes are expected, update the |
c6a7d36 to
7d06234
Compare
| return html` | ||
| <sp-overlay | ||
| id="truncated-value-tooltip" | ||
| aria-hidden="true" |
There was a problem hiding this comment.
There was a problem hiding this comment.
My concern with this is that a non-sighted user may think the same text is entered twice and attempt to delete the extra text.
| el.value = 45; | ||
| expect(el.value).to.equal(45); | ||
| el.focus(); | ||
| await sendKeys({ type: '7' }); // Visible text: EUR 45.007 |
There was a problem hiding this comment.
Just linting fixes in this file below this point
There was a problem hiding this comment.
Liked the approach here. Most of the direction looks sound to me except for a few areas which needs attention. One major blocker is the the password field security issue and the added dependencies on the textfield is a bundle size concern for me, we can discuss this with the team and the eager ResizeObserver allocation will hinder performance. Try to optimize and think of a more performant approach.
Most of the other things looks solid and thanks for handling this a11y compliance. Happy to work with you in resolving the blocker.
| "@spectrum-web-components/overlay": "1.11.2", | ||
| "@spectrum-web-components/shared": "1.11.2", | ||
| "@spectrum-web-components/tooltip": "1.11.2" |
There was a problem hiding this comment.
Adding @spectrum-web-components/overlay and @spectrum-web-components/tooltip as runtime dependencies of textfield is a bit concerning architecturally as every consumer of these components now pay the cost of loading overlay and tooltip even if truncation never occurs.
It would be great if you can lazy load these dependencies when truncation is first detected.
private _tooltipDepsLoaded = false;
private async ensureTooltipDeps(): Promise<void> {
if (!this._tooltipDepsLoaded) {
await import('@spectrum-web-components/overlay/sp-overlay.js');
await import('@spectrum-web-components/tooltip/sp-tooltip.js');
this._tooltipDepsLoaded = true;
}
}You can create a mixin pattern also so that you are not baking this into the base class.
| super.firstUpdated(changedProperties); | ||
| this.truncationResizeObserver.observe(this); | ||
| if (this.inputElement) { | ||
| this.truncationResizeObserver.observe(this.inputElement); | ||
| } | ||
| this.refreshTruncationState(); | ||
| } |
There was a problem hiding this comment.
In case of multiple textfields this will still attach the ResizeObserver and runs refreshTruncationState() but unputElementIsTruncated will return false for multiline. The observer will still fire on every resize. Can you guard this for multiline?
if(!this.multiline) {
this.truncationResizeObserver.observe(this);
}
nikkimk
left a comment
There was a problem hiding this comment.
I think we might need to discuss our options with this one given the a11y concerns.
| return html` | ||
| <sp-overlay | ||
| id="truncated-value-tooltip" | ||
| aria-hidden="true" |
There was a problem hiding this comment.
My concern with this is that a non-sighted user may think the same text is entered twice and attempt to delete the extra text.
5t3ph
left a comment
There was a problem hiding this comment.
In addition to being concerned about the issue for screen readers, I noticed that for number field the tooltip does not reflect real-time edits to the value (deletions or insertions).
The value does update real-time for textfield, but still with a bit of oddness that the tooltip is removed if the value decreases below the threshold (which is a little jarring, possible cognitive issue ex "wait, what just happened? did I do something wrong?"). But, it doesn't re-appear when the value increases above the threshold. Not reappearing might be ok, since the user is actively in the field and their cursor position is still allowing them to see the relevant part that they're editing.
4d57b24 to
6dffaa6
Compare
- change name of resize observer so it doesn't clash - add guard for inputElement
- also add aria-hidden to tooltip
- Do not reflect tooltip-placement - Trigger refreshTruncationState from update() only on value change - Run observer setup and refreshTruncationState only when !multiline - Test import remains from '../'
7ccfd84 to
aebf001
Compare
| } | ||
|
|
||
| renderTruncatedValueTooltip(): TemplateResult | typeof nothing { | ||
| protected renderTruncatedValueTooltip(): TemplateResult | typeof nothing { |
There was a problem hiding this comment.
I assumed we wanted this to be protected, but am happy to revert if not.
| this._trackingValue = value; | ||
| this.inputElement.value = value; | ||
| this.inputElement.setSelectionRange(selectionStart, selectionStart); | ||
| this.syncTruncatedValueTooltipText(); |
There was a problem hiding this comment.
Number-field's tooltip wasn't updating the text in real-time if edited. This addresses that issue.
My understanding is that it's needed in number-field but not textfield because of the value formatting that number-field does.
There was a problem hiding this comment.
| this.inputElement.value = value; | ||
| this.inputElement.setSelectionRange(selectionStart, selectionStart); | ||
| this.syncTruncatedValueTooltipText(); | ||
| this.refreshTruncatedValueTooltipState(); |
There was a problem hiding this comment.
Number-field's tooltip also wasn't appearing in some cases because it wasn't checking properly for truncation, so refreshTruncatedValueTooltipState addresses that.
| :host([type="password"]) .input { | ||
| text-overflow: clip; | ||
| } | ||
|
|



Description
Adds truncated-value tooltip behavior to single-line text inputs in
sp-textfieldandsp-number-field.Updates (2/27/26):
describeTriggerproperty to overlay to prevent overlay from settingaria-describedby, thus preventing screen reader double announcementMotivation and context
Long values in narrow fields can be difficult to review, especially while correcting invalid input.
This change improves usability by exposing the full value on hover/focus only when truncation occurs, while keeping default behavior unchanged when content fits.
Related issue(s)
Screenshots (if appropriate)
Author's checklist
Reviewer's checklist
patch,minor, ormajorfeaturesManual review test cases
Textfield shows tooltip only when visually truncated
Textfield / truncatedValueTooltipstory.Invalid truncated textfield still exposes full value
Number-field truncation behavior works with and without stepper
Number Field / truncatedValueTooltipstory.Regression checks
Updates (2/27/26):
Device review
Accessibility testing checklist
Required: Complete each applicable item and document your testing steps (replace the placeholders with your component-specific instructions).
Keyboard (required — document steps below) — What to test for: Focus order is logical; Tab reaches the component and all interactive descendants; Enter/Space activate where appropriate; arrow keys work for tabs, menus, sliders, etc.; no focus traps; Escape dismisses when applicable; focus indicator is visible.
Screen reader (required — document steps below) — What to test for: Role and name are announced correctly; state changes (e.g. expanded, selected) are announced; labels and relationships are clear; no unnecessary or duplicate announcements.
aria-describedby; addressing this felt a bit out-of-scope