Skip to content

feat: Add filtering to button dropdown#4637

Merged
avinashbot merged 5 commits into
mainfrom
feat/actions-search
Jul 9, 2026
Merged

feat: Add filtering to button dropdown#4637
avinashbot merged 5 commits into
mainfrom
feat/actions-search

Conversation

@avinashbot

@avinashbot avinashbot commented Jun 16, 2026

Copy link
Copy Markdown
Member

Description

Button dropdown action filtering implemented a similar way to select/multiselect filtering. The big underlying change is that filtering requires the use of aria-activedescendant rather than simply moving focus from item to item. So most of the internal changes are about disabling focusing logic when filtering is active, so that the real focus can stay on the input (and "accessibility" focus can move around using aria-activedescendant).

Rel: AWSUI-61960 (Chorus: qodG8KXdJXXk)

How has this been tested?

Updated unit tests, a bit of manual accessibility testing, but more validation with other screen readers is needed.

Review checklist

The following items are to be evaluated by the author(s) and the reviewer(s).

Correctness

  • Changes include appropriate documentation updates.
  • Changes are backward-compatible if not indicated, see CONTRIBUTING.md.
  • Changes do not include unsupported browser features, see CONTRIBUTING.md.
  • Changes were manually tested for accessibility, see accessibility guidelines.

Security

Testing

  • Changes are covered with new/existing unit tests?
  • Changes are covered with new/existing integration tests?

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.72611% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.59%. Comparing base (067bea5) to head (373fd9b).

Files with missing lines Patch % Lines
.../category-elements/expandable-category-element.tsx 77.77% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4637      +/-   ##
==========================================
+ Coverage   97.57%   97.59%   +0.02%     
==========================================
  Files         948      950       +2     
  Lines       30517    30642     +125     
  Branches    11168    11229      +61     
==========================================
+ Hits        29777    29906     +129     
+ Misses        693      689       -4     
  Partials       47       47              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@avinashbot avinashbot force-pushed the feat/actions-search branch from 0d5d891 to 14e5718 Compare June 18, 2026 23:10
@avinashbot avinashbot force-pushed the feat/actions-search branch from 14e5718 to b0fd947 Compare June 18, 2026 23:29
@avinashbot avinashbot force-pushed the feat/actions-search branch 2 times, most recently from 042705d to 50e2c55 Compare June 23, 2026 16:07
@avinashbot avinashbot force-pushed the feat/actions-search branch from 50e2c55 to d5f7211 Compare June 24, 2026 10:57
Comment thread src/button-dropdown/__tests__/button-dropdown-filtering.test.tsx
Comment thread src/button-dropdown/__tests__/button-dropdown-filtering.test.tsx
Comment thread src/button-dropdown/__tests__/button-dropdown-filtering.test.tsx Outdated
Comment thread src/button-dropdown/__tests__/button-dropdown-filtering.test.tsx Outdated
Comment thread src/button-dropdown/__tests__/button-dropdown-filtering.test.tsx Outdated
Comment thread src/button-dropdown/__tests__/button-dropdown-filtering.test.tsx
expect(footer!.getElement()).toHaveTextContent('No items found');
});

test('counts leaf items in groups for totalCount', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is inconsistent with multiselect - that does include groups to filtering results text. Most importantly, the filtering also works inconsistently: in multiselect one can filter for the group name, e.g. "Group 1" - it then shows all items that belong to this group. Why is this different in the button dropdown? E.g. in the demo I type "Edit" to the filter to find all items that belong to the "Edit" category. The result is empty though.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh, good callout. I don't know what the intended behavior should be here actually, need to confirm with design. We had this behavior implicltly from the original prototype and signoff but I don't know if this was ever discussed.

But I would imagine behaving like select/multiselect could be pretty unhelpful here, especially for really large dropdowns (like with EC2), where typing in "instance" would get you an really big list of items rather than a specific action you were maybe looking for.

Let's assume this is correct for now, but I won't merge until I get an approval on this specific behavior from the designer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this can be misleading. E.g. when I search for "Security" in your demo - it matches one item our of two that are defined inside the "Security" group - because that one happens to have "security" keyword in the description. It is true that matching groups can result in many items in the output - but this can be mitigated by clarifying the input, e.g. saying "change security" or "security groups" would then find the item alone, no longer matching every item in the group.

@avinashbot avinashbot Jul 9, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The trouble is also that it's not consistent. For nested expandable groups (for example: "Networking" in the nested expandable group demo in the filtering dev page), the inner group name is intentionally discarded when filtering, because we don't want to have nested flattened groups. So the outer group name is filterable, but the inner one isn't. For example, typing in "Diagnostics" in the nested demo gets you no results. The alternative is to match the items under the nested group, but not show the parent group, which makes it equally confusing why they were matched.

Screenshot 2026-07-09 at 14 38 31

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Had a chat with the designer and decided to keep this behavior.

  • This (group filtering) wasn't part of the initial scope internal or in discussion with the building team. Adding this (especially having to re-address the group "collapsing" topic we already had an agreement on earlier) is going to expand the scope for no clear use-case.
  • The component is different to select and multiselect; the use-case of getting to specific actions without digging through menus is different to the select/multiselect components more "exploratory" use-case.
  • Because the API for this is pretty opaque, we could treat this as a two-way door to introduce in the future if we really had strong data pushing us to do so. And this ("auto" filtering) is just a stepping stone to supporting more complex filtering in the future.

);
});

test('passes filterText to renderItem for checkbox items', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what is the purpose of checking for different item types now that filtering text is passed as a separate arg?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Test coverage! This logic is actually duplicated three times, and each test tests a different place where the check is defined.

Not sure how much sense deduplicating it makes, it's just one additional prop in the event handler call.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we have a single test then that defines a button dropdown featuring all item types?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure. But to make sure callback assertions are correct, I still need to trigger each item individually. So not much is deduplicated.

Comment thread src/button-dropdown/item-element/index.tsx
Comment thread src/button-dropdown/item-element/index.tsx Outdated
position?: string;
renderItem?: ButtonDropdownProps.ItemRenderer;
filteringText?: string;
filteringEnabled?: boolean;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: I would either make filteringText required, or remove filteringEnabled in favour of filteringText !== undefined check.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Felt it was cleaner this way to have a boolean rather than combining these two attributes into an | undefined union. hasFiltering is easy to determine at the top-level from the filteringType prop, and for child elements, it's easier to pass the boolean straight through to effect dependencies (for example: expandable-category-element.tsx) rather than create a separate variable (const hasFiltering = filteringText !== undefined) just for effects (reversing additional logic in use-button-dropdown.tsx, which would need to set filteringText: hasFiltering ? filteringText : undefined).

Don't feel super strongly about this though, it's just a minor refactor. Let me know if that makes sense or you'd still prefer condensing this into one state variable.

filteringResultsText: filteredText,
});

// Only create a filteringDescription element if filtering is actually enabled,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: I think this comment is redundant as the code is clear enough

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The comment describes what the hook does internally; easy to check the source, but I felt it important to call out here, since a seemingly innocent refactor to not match the existence of the element with the existence of the id could create an accessibility issue.

Comment thread src/test-utils/dom/button-dropdown/index.ts Outdated
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.

3 participants