Skip to content

feat(textfield, number-field): add tooltip for a11y on truncated input text#6038

Open
rise-erpelding wants to merge 20 commits intomainfrom
rise-erpelding/swc-1183-text-spacing-cutoff
Open

feat(textfield, number-field): add tooltip for a11y on truncated input text#6038
rise-erpelding wants to merge 20 commits intomainfrom
rise-erpelding/swc-1183-text-spacing-cutoff

Conversation

@rise-erpelding
Copy link
Collaborator

@rise-erpelding rise-erpelding commented Feb 19, 2026

Description

Adds truncated-value tooltip behavior to single-line text inputs in sp-textfield and sp-number-field.

  • Detects when the visible value is clipped and conditionally renders a tooltip with the full value.
  • Applies behavior to both valid and invalid values (when truncated).
  • Includes updates to tests, stories, docs, and changeset coverage.

Updates (2/27/26):

  • Does not show tooltip for password and removes truncation for password fields
  • Adds describeTrigger property to overlay to prevent overlay from setting aria-describedby, thus preventing screen reader double announcement
  • Breaks tooltip feature out into a mixin which lazy-loads tooltip and overlay
  • Although it worked in textfield, number-field was not reflecting real-time edits within the tooltip, so a fix was made to keep truncated tooltip in sync with edits
  • In number-fields whose content went from untruncated to truncated, the tooltip was not showing up - this was fixed
  • Tooltip is now maintained during focus (while typing), it doesn't suddenly disappear when content is small enough to not need truncation

Motivation 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)

  • fixes SWC-1183

Screenshots (if appropriate)

image image

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Textfield shows tooltip only when visually truncated

    1. Open Textfield / truncatedValueTooltip story.
    2. Hover and keyboard-focus the truncated example.
    3. Verify full value is shown in tooltip.
    4. Verify non-truncated example does not show tooltip.
  • Invalid truncated textfield still exposes full value

    1. Use the invalid truncated textfield story/example.
    2. Hover/focus the field.
    3. Verify tooltip appears with the full value even while invalid.
  • Number-field truncation behavior works with and without stepper

    1. Open Number Field / truncatedValueTooltip story.
    2. Verify tooltip appears for truncated examples (stepper visible and hidden).
    3. Verify formatted currency text appears in tooltip.
  • Regression checks

    1. Verify multiline textfield behavior is unchanged (no new truncation tooltip behavior introduced there).
    2. Verify no regressions in validation icon/help-text rendering.

Updates (2/27/26):

  • Check password field in Textfield truncation stories, confirm that this does not have truncation ellipses and does not show the password in a tooltip
  • Use screen-reader on truncated textfield to determine that the text input is not double announced
  • Use screen-reader on truncated numberfield to determine that the number input is not double announced
  • Check truncated number-fields, when editing truncated content it should update the value in real time (Note: it doesn't re-place the commas in real-time... yet)
  • Type in a number long enough (such as 12345678) to truncate in the number-field story to confirm that the truncation tooltip appears in number-fields that are not initially truncated
  • On that same number-field story with a number that is long enough to truncate (such as 12345678), remove characters from the input so that it is short enough to not need truncation (for instance, change the input from 12345678 to 12345). Verify that the tooltip does not disappear until you mouse/key focus somewhere else, and when you focus on the number-field again (with the shorter number inputted), the tooltip does not reappear.
  • Check a textfield story for the same. If text input truncates, you should see the tooltip while typing, and if the text becomes shortened enough to not need truncation, the tooltip still appears until the next time the input is focused.

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

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.

    1. Go to truncated numberfield stories
    2. Tab to the first number-field input
    3. When focused, the tooltip displaying the full value should be 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.

    1. Go truncated textfield stories
    2. Turn on VoiceOver
    3. Expect truncated input values to be read - they actually are being read twice due to the overlay setting aria-describedby; addressing this felt a bit out-of-scope

@rise-erpelding rise-erpelding self-assigned this Feb 19, 2026
@changeset-bot
Copy link

changeset-bot bot commented Feb 19, 2026

🦋 Changeset detected

Latest commit: 425a701

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 83 packages
Name Type
@spectrum-web-components/overlay Patch
@spectrum-web-components/textfield Patch
@spectrum-web-components/number-field Patch
@spectrum-web-components/action-menu Patch
@spectrum-web-components/combobox Patch
@spectrum-web-components/contextual-help Patch
@spectrum-web-components/menu Patch
@spectrum-web-components/picker Patch
@spectrum-web-components/popover Patch
@spectrum-web-components/tooltip Patch
@spectrum-web-components/story-decorator Patch
@spectrum-web-components/bundle Patch
@spectrum-web-components/truncated Patch
@spectrum-web-components/color-field Patch
@spectrum-web-components/search Patch
@spectrum-web-components/slider Patch
@spectrum-web-components/breadcrumbs Patch
@spectrum-web-components/custom-vars-viewer Patch
@spectrum-web-components/action-bar Patch
@spectrum-web-components/card Patch
@spectrum-web-components/coachmark Patch
documentation Patch
@spectrum-web-components/vrt-compare Patch
@spectrum-web-components/accordion Patch
@spectrum-web-components/action-button Patch
@spectrum-web-components/action-group Patch
@spectrum-web-components/alert-banner Patch
@spectrum-web-components/alert-dialog Patch
@spectrum-web-components/asset Patch
@spectrum-web-components/avatar Patch
@spectrum-web-components/badge Patch
@spectrum-web-components/button-group Patch
@spectrum-web-components/button Patch
@spectrum-web-components/checkbox Patch
@spectrum-web-components/clear-button Patch
@spectrum-web-components/close-button Patch
@spectrum-web-components/color-area Patch
@spectrum-web-components/color-handle Patch
@spectrum-web-components/color-loupe Patch
@spectrum-web-components/color-slider Patch
@spectrum-web-components/color-wheel Patch
@spectrum-web-components/dialog Patch
@spectrum-web-components/divider Patch
@spectrum-web-components/dropzone Patch
@spectrum-web-components/field-group Patch
@spectrum-web-components/field-label Patch
@spectrum-web-components/help-text Patch
@spectrum-web-components/icon Patch
@spectrum-web-components/icons-ui Patch
@spectrum-web-components/icons-workflow Patch
@spectrum-web-components/icons Patch
@spectrum-web-components/iconset Patch
@spectrum-web-components/illustrated-message Patch
@spectrum-web-components/infield-button Patch
@spectrum-web-components/link Patch
@spectrum-web-components/meter Patch
@spectrum-web-components/modal Patch
@spectrum-web-components/picker-button Patch
@spectrum-web-components/progress-bar Patch
@spectrum-web-components/progress-circle Patch
@spectrum-web-components/radio Patch
@spectrum-web-components/sidenav Patch
@spectrum-web-components/split-view Patch
@spectrum-web-components/status-light Patch
@spectrum-web-components/swatch Patch
@spectrum-web-components/switch Patch
@spectrum-web-components/table Patch
@spectrum-web-components/tabs Patch
@spectrum-web-components/tags Patch
@spectrum-web-components/thumbnail Patch
@spectrum-web-components/toast Patch
@spectrum-web-components/top-nav Patch
@spectrum-web-components/tray Patch
@spectrum-web-components/underlay Patch
@spectrum-web-components/base Patch
@spectrum-web-components/grid Patch
@spectrum-web-components/opacity-checkerboard Patch
@spectrum-web-components/reactive-controllers Patch
@spectrum-web-components/shared Patch
@spectrum-web-components/styles Patch
@spectrum-web-components/theme Patch
@spectrum-web-components/eslint-plugin Patch
@spectrum-web-components/stylelint-header-plugin Patch

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

@rise-erpelding rise-erpelding added the Status:WIP PR is a work in progress or draft label Feb 19, 2026
@rise-erpelding rise-erpelding force-pushed the rise-erpelding/swc-1183-text-spacing-cutoff branch 2 times, most recently from a03db36 to c46b5e3 Compare February 19, 2026 02:45
@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 2026

📚 Branch Preview Links

🔍 First Generation Visual Regression Test Results

When 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: pr-6038

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@rise-erpelding rise-erpelding force-pushed the rise-erpelding/swc-1183-text-spacing-cutoff branch 2 times, most recently from c6a7d36 to 7d06234 Compare February 19, 2026 22:03
return html`
<sp-overlay
id="truncated-value-tooltip"
aria-hidden="true"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that the truncated inputs were being read twice when using VoiceOver, I did apply aria-hidden which seems to remove it from the accessibility tree at least? My understanding is that this is due to the way that sp-overlay is written, touching overlay seems little out of scope 😬

Image

Copy link
Contributor

@nikkimk nikkimk Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rise-erpelding rise-erpelding marked this pull request as ready for review February 19, 2026 22:18
@rise-erpelding rise-erpelding requested a review from a team as a code owner February 19, 2026 22:18
@rise-erpelding rise-erpelding added Status:Ready for review PR ready for review or re-review. Component:Textfield a11y Issues or PRs related to accessibility Component:Number field and removed Status:WIP PR is a work in progress or draft labels Feb 19, 2026
@rise-erpelding rise-erpelding removed their assignment Feb 19, 2026
el.value = 45;
expect(el.value).to.equal(45);
el.focus();
await sendKeys({ type: '7' }); // Visible text: EUR 45.007
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just linting fixes in this file below this point

Copy link
Contributor

@Rajdeepc Rajdeepc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +62 to +64
"@spectrum-web-components/overlay": "1.11.2",
"@spectrum-web-components/shared": "1.11.2",
"@spectrum-web-components/tooltip": "1.11.2"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 436 to 442
super.firstUpdated(changedProperties);
this.truncationResizeObserver.observe(this);
if (this.inputElement) {
this.truncationResizeObserver.observe(this.inputElement);
}
this.refreshTruncationState();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}

@rise-erpelding rise-erpelding added Status:Addressing feedback PR owner is addressing review comments and will change label back to "Ready for review" when ready. and removed Status:Ready for review PR ready for review or re-review. labels Feb 20, 2026
Copy link
Contributor

@nikkimk nikkimk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"
Copy link
Contributor

@nikkimk nikkimk Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

@5t3ph 5t3ph left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rise-erpelding rise-erpelding added Status:Blocked PR is blocked for some reason and removed Status:Blocked PR is blocked for some reason labels Feb 23, 2026
@rise-erpelding rise-erpelding force-pushed the rise-erpelding/swc-1183-text-spacing-cutoff branch 2 times, most recently from 4d57b24 to 6dffaa6 Compare February 26, 2026 17:50
@rise-erpelding rise-erpelding force-pushed the rise-erpelding/swc-1183-text-spacing-cutoff branch from 7ccfd84 to aebf001 Compare February 27, 2026 16:35
}

renderTruncatedValueTooltip(): TemplateResult | typeof nothing {
protected renderTruncatedValueTooltip(): TemplateResult | typeof nothing {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Collaborator Author

@rise-erpelding rise-erpelding Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting also that the tooltip isn't parsing & formatting the values in real-time, so it's possible that when editing the number you'll see odd formatting in the tooltip like this:

Image

I could address it here, it could be addressed later, or something else. Any thoughts?

this.inputElement.value = value;
this.inputElement.setSelectionRange(selectionStart, selectionStart);
this.syncTruncatedValueTooltipText();
this.refreshTruncatedValueTooltipState();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Number-field's tooltip also wasn't appearing in some cases because it wasn't checking properly for truncation, so refreshTruncatedValueTooltipState addresses that.

Comment on lines +278 to +281
:host([type="password"]) .input {
text-overflow: clip;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once I added a password field in Storybook, I realized how odd it looks when it truncates:

Image

This removes that truncation. This felt a little out of scope though so I'm happy to revert if we don't want this change in here!

@rise-erpelding rise-erpelding added Status:Ready for review PR ready for review or re-review. Status:Ready for re-review PR has had its feedback addressed and is once again ready for review. and removed Status:Addressing feedback PR owner is addressing review comments and will change label back to "Ready for review" when ready. Status:Ready for review PR ready for review or re-review. labels Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a11y Issues or PRs related to accessibility Component:Number field Component:Textfield Status:Ready for re-review PR has had its feedback addressed and is once again ready for review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants