Skip to content

fix(picker): migrated picker to FieldLabelMixin and accessible implementation of select-only combobox pattern#6090

Draft
nikkimk wants to merge 20 commits intonikkimk/form-field-mixinfrom
nikkimk/SWC-1449-picker-as-combobox
Draft

fix(picker): migrated picker to FieldLabelMixin and accessible implementation of select-only combobox pattern#6090
nikkimk wants to merge 20 commits intonikkimk/form-field-mixinfrom
nikkimk/SWC-1449-picker-as-combobox

Conversation

@nikkimk
Copy link
Copy Markdown
Contributor

@nikkimk nikkimk commented Mar 18, 2026

Description

  • Updated picker:
    • migrated picker to use FieldLabelMixin
    • updated picker to use accessible implementation of select-only combobox pattern
    • updated picker to apply labels and placeholders correctly, including cross browser support for icons-only implementation
    • updated label warning
    • added deprecation warning for using <sp-field-label> in picker
    • added deprecation warning for using label as a placeholder in picker
  • Updated documentation:
    • ensured labels and placeholders accurately described picker content in order to model accessible usage
    • updated examples to use slotted field-label
    • added deprecation notice for using <sp-field-label> in picker
    • updated examples to use placeholder
    • added deprecation notice for using label as a placeholder in picker
  • Updated stories:
    • ensured labels and placeholders accurately described picker content in order to model accessible usage
    • updated examples to use slotted field-label
    • updated examples to use placeholder
    • kept a deprecated example using <sp-field-label> in picker
    • kept a deprecated example for using label as a placeholder in picker
  • Updated tests:
    • updated tests based on the updated labels and placeholders in stories
    • updated tests based on the correct expectations for name and value in a11y tree, including cross browser support for icons-only implementation
    • added tests for deprecation warning for using <sp-field-label> in picker
    • added tests for deprecation warning for using label as a placeholder in picker

Motivation and context

Add placeholder functionality to Picker components to provide better user experience and address consumer misuse of label attributes as placeholders. While placeholders are controversial in accessibility circles, there is still interest in implementing them as evidenced by design team discussions. This feature will introduce proper placeholder support while maintaining backward compatibility and accessibility compliance.

Related issue(s)

  • fixes 1044

Screenshots (if appropriate)


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

Ignore the following failing tests:

  • VRTs: There is a separate ticket to fix the CSS regressions on the nikkimk/form-field-mixin branch
  • Two textfield tests. These are out of scope and need to be fixed as part of the final ticket to prepare nikkimk/form-field-mixin branch to be merged
  • Code coverage. Additional deprecation tests will be added as part of another ticket to prepare nikkimk/form-field-mixin branch to be merged

Using the picker documentation and Storybook examples:

  • Verify placeholder text displays correctly in all components
  • Confirm deprecation warnings appear in console
  • Test all slot combinations work as expected
  • Validate accessibility warnings fire appropriately
  • Check backward compatibility with existing implementations
  • Verify documentation examples render correctly
  • Test keyboard navigation and screen reader behavior

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 here
    2. Do this action
    3. Expect this result
  • 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 here
    2. Do this action
    3. Expect this result

@nikkimk nikkimk self-assigned this Mar 18, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 18, 2026

⚠️ No Changeset found

Latest commit: 3f26616

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@nikkimk nikkimk changed the base branch from main to nikkimk/form-field-mixin March 18, 2026 22:20
@github-actions
Copy link
Copy Markdown
Contributor

📚 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-6090

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.

@nikkimk nikkimk changed the title Nikkimk/swc 1449 picker as combobox fix(picker): migrated picker to formFieldMixin and accessible implmentation of select-only combobox pattern Mar 20, 2026
@nikkimk nikkimk changed the title fix(picker): migrated picker to formFieldMixin and accessible implmentation of select-only combobox pattern fix(picker): migrated picker to FieldLabelMixin and accessible implementation of select-only combobox pattern Mar 20, 2026
@nikkimk nikkimk marked this pull request as ready for review March 20, 2026 15:32
@nikkimk nikkimk requested a review from a team as a code owner March 20, 2026 15:32
Copy link
Copy Markdown
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.

Like the architectural direction here. I have left a few suggestions to strengthen the implementation, and called out some blockers inline that need to be addressed.
There are also a few areas I’d like us to discuss before I can give a final pass on this —happy to sync when you’re ready. Thanks for driving this forward.

Suggestion:
hasAccessibleLabel has 6 @todo statements. Can we add tracking issues and reference them inline. Happy to sync to calibrate which issue is worth investing on priority before we merge this as I feel cross root ARIA issue is important as they could effect the accessibility of the entire label chain?

aria-haspopup="true"
aria-labelledby="icon label applied-label pending-label"
aria-haspopup="listbox"
aria-labelledby="${ariaLabelledBy} pending-label"
Copy link
Copy Markdown
Contributor

@Rajdeepc Rajdeepc Mar 23, 2026

Choose a reason for hiding this comment

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

blocker: Any consumers relying on JS targeting will break silently. We have removed icon and label it seems. Should we document this in a migration table?

*/
@state()
protected get selectedItemContent(): MenuItemChildren {
public get selectedItemContent(): MenuItemChildren {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is expanding the API surface. If this mixin needs internal access how about a symbol-based approach? Something like

const SELECTED_ITEM_CONTENT = Symbol('selectedItemContent');

// In the mixin or class:
[SELECTED_ITEM_CONTENT]: MenuItemChildren | undefined;

If this is truly needed to be public for a documented consumer use case, Can you please add it to the component's JSDoc @Property documentation and include it in the changeset as a new API addition.

'ArrowRight event should not propagate'
).to.equal(0);

//@nikki
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remove this?

placeholder="Choose a selection type with a very long label, too long, in fact"
${spreadProps(args)}
>
<span slot="field-label">Selection type:</span>
Copy link
Copy Markdown
Contributor

@Rajdeepc Rajdeepc Mar 23, 2026

Choose a reason for hiding this comment

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

blocker: This is the new pattern. You have an a11y test for this that references this story which will not validate the deprecation path. Can you update this story to use the deprecated sp-field-label element?

export const DeprecatedSpFieldLabel = (args: StoryArgs): TemplateResult => {
  return html`
    <sp-field-label for="picker-deprecated">Selection type:</sp-field-label>
    <sp-picker
      id="picker-deprecated"
      @change=${handleChange(args)}
      placeholder="Choose a selection type"
      ${spreadProps(args)}
    >
      <sp-menu-item value="option-1">Deselect</sp-menu-item>
      <!-- ... -->
    </sp-picker>
  `;
};

// Chrome adds labels and alt text to the icon content, so we need to remove them
// since we're already capturing them as text for Firefox and WebKit
// and otherwise our fix for Firefox and Safari would cause the label to be read twice
const icon = this.selectedItemContent?.icon?.map((node) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is cloning icons and stripping attributes on every render which is expensive as it creates new DOM nodes on every render cycle. This can recreate the DOM subtree each time. The best approach here would be to process icons once when the selection changes and not on every render, you can do this in selectedItem setter. A rough idea would be something like this.

@state()
private _processedIcons?: Node[];

public set selectedItem(selectedItem: MenuItem | undefined) {
  this.selectedItemContent = selectedItem
    ? selectedItem.itemChildren
    : undefined;

 // Process icons once on selection change
  this._processedIcons = this.selectedItemContent?.icon?.map((node) => {
    const clone = node.cloneNode(true) as HTMLElement;
    clone.removeAttribute('label');
    clone.removeAttribute('alt');
    return clone;
  });

.....

}

Now In buttonContent getter, use this._processedIcons instead of cloning

applyFocusElementLabel = (value: string, labelElement: FieldLabel): void => {
this.appliedLabel = value;
this.labelAlignment = labelElement.sideAligned ? 'inline' : undefined;
window.__swc.warn(
Copy link
Copy Markdown
Contributor

@Rajdeepc Rajdeepc Mar 23, 2026

Choose a reason for hiding this comment

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

blocker: This will throw a runtime error. Let's check if (window.__swc?.DEBUG) before we call warn?

Comment on lines -1977 to -1985
if (!this.hasUpdated && this.querySelector(':scope > sp-menu')) {
const { localName } = this;
window.__swc.warn(
this,
`You no longer need to provide an <sp-menu> child to ${localName}. Any styling or attributes on the <sp-menu> will be ignored.`,
'https://opensource.adobe.com/spectrum-web-components/components/picker/#sizes',
{ level: 'deprecation' }
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we keep this till the sp-menu support is actually dropped. This seems to be a premature removal, there are many consumers who are yet to fully migrate to our latest.


```html demo
<sp-field-label for="uses-sp-field-label">Selection type:</sp-field-label>
<sp-field-label for="uses-sp-field-label"></sp-field-label>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's add a text content here. :D

<sp-picker
id="picker-loading"
label="Loading blending modes..."
pending
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Did we add any replacement for this pending-label property example?

const el = test.querySelector('sp-picker') as Picker;

await elementUpdated(el);
const picker = el.querySelector('sp-picker') as Picker;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

el is already the picker, so el.querySelector('sp-picker') is looking for a nested picker. The if (picker) guard silently no-ops. You can remove this dead code.

@nikkimk nikkimk marked this pull request as draft March 23, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants