Skip to content

feat: add multi-column sort to Table#4713

Open
gethinwebster wants to merge 12 commits into
mainfrom
dev-v3-gethinw-multi-column-sort
Open

feat: add multi-column sort to Table#4713
gethinwebster wants to merge 12 commits into
mainfrom
dev-v3-gethinw-multi-column-sort

Conversation

@gethinwebster

@gethinwebster gethinwebster commented Jul 10, 2026

Copy link
Copy Markdown
Member

Description

Adds opt-in multi-column sorting for Table through multiColumnSort.

Customers can sort by multiple columns using Shift+click or Shift+keyboard activation, manage each column through a sort-options menu, see numbered sort priorities, and clear the active sort. The change also adds screen-reader announcements, localized strings, public test-utils support, generated API documentation, and demo/permutation pages.

The public API is additive and opt-in. Existing single-column sorting behavior is unchanged.

Related links, issue #: n/a

How has this been tested?

Added new unit tests

Review checklist

Correctness

  • Changes include public API and test-utils documentation updates.
  • Changes are backward-compatible: multiColumnSort is optional and single-column sorting is unchanged.
  • Changes do not introduce unsupported browser features.
  • Manual browser accessibility validation was not run locally because ChromeDriver is unavailable; targeted accessibility behavior is covered by unit tests and browser validation requires CI or a ChromeDriver-enabled environment.

Security

  • The change does not add URL handling.

Testing

  • Changes are covered by unit tests, including keyboard interaction, comparator columns, screen-reader priority text, sort-menu actions, clearing, warnings, live announcements, and test utilities.
  • Browser integration testing was not run locally because ChromeDriver is unavailable; verify in CI or a ChromeDriver-enabled environment.

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

gethinwebster and others added 2 commits July 10, 2026 09:21
Add multi-column sorting to Table, activated by shift-clicking sortable
column headers or via a per-column sort menu.

- Per-column sort menu: set ascending/descending direction (shown as a
  checked state), add to / remove from the multi-column sort, with
  disabled-reason tooltips on unavailable actions.
- Priority badges on sorted column headers and a Clear sort control.
- A polite live region announcing the current sort order (and when sorting
  is cleared), reading localized header text from the DOM and joining
  columns with locale-appropriate separators.
- Focus management so clearing the sort keeps keyboard focus on a sortable
  column header instead of dropping to the document body.
- Styling fixes so the sort icon and priority badge do not overlap
  truncated header text in narrow columns.
- i18n strings for all new labels and screen reader announcements, plus
  test-utils and unit tests covering the new behavior.
Adds translations for the multi-column sort strings across supported locales.
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.61%. Comparing base (3ad0425) to head (387ba53).
⚠️ Report is 24 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4713      +/-   ##
==========================================
+ Coverage   97.59%   97.61%   +0.01%     
==========================================
  Files         950      955       +5     
  Lines       30642    30926     +284     
  Branches    11229    11368     +139     
==========================================
+ Hits        29906    30189     +283     
- Misses        689      730      +41     
+ Partials       47        7      -40     

☔ 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.

…ection + priority strings

Renames i18nStrings.sortDropdown.* to i18nStrings.sortDropdownX and replaces the
single i18nStrings.liveAnnouncementSortColumn ICU string with ariaLabels.sortAscending/
sortDescending + ariaLabels.sortPriority, composed by ariaLabels.liveAnnouncementSortOrder.
Matches the restructured AWS-UI-Components-I18n source (merged in CR-289393273).

Includes best-guess translations for 12 locales for the renamed/new keys; real
Totoro translations to follow.
Comment thread pages/table/multi-column-sort.page.tsx Outdated
>();

// `multiSort` defaults to on; the rest default to off.
const multiSortEnabled = urlParams.multiSort !== 'false' && urlParams.multiSort !== false;

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 chunk repeats the same type of conversion 6 times and we even already have it here:

if (value === 'true' || value === 'false') {
return value === 'true';
}

Can we deduplicate this?

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.

removed, wasn't needed

<PermutationsPage title="Table multi-column sort permutations" i18n={{}}>
<PermutationsView
permutations={permutations}
render={permutation => (

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.

Minor: render takes the array index as second argument, so this could be

render={(permutation, index) => (
          <Table
            {...permutation}
            ariaLabels={{ ...ariaLabels, tableLabel: `Items ${index}` }}
          />
        )}

Also, is this ARIA label very meaningful?

Comment thread src/button-dropdown/styles.scss Outdated

// Removes the trigger's vertical padding and borders so an `icon`-variant trigger matches the
// compact `inline-icon` footprint. The class is doubled to override the button variant styles
// (`.button.variant-icon`), which button-dropdown CSS is loaded after.

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.

Are we 100% sure that this stylesheet is always loaded after the button styles in all cases (what if for example components are loaded asynchronously), and that selector specificity is not going to break this?

What about delegating this class to the button component? There we can even do something like this to make sure we don't mix both styles:

.variant-icon:not(.compact), (...) { ... }

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.

good catch, moved to button styling

const { container } = render(
<Table
items={items}
columnDefinitions={extraColumns ?? columnDefinitions}

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: "extra" suggests that they will be added rather than replace the default columnDefinitions. What about renaming extraColumns as columnDefinitions and columnDefinitions as defaultColumnDefinitions?

Comment thread src/table/styles.scss
margin-inline-start: auto;
}

&-clear-sort {

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.

The slot only ever contains one element (the clear sort button), so I don't think these flex styling is needed

}
return (
<InternalLiveRegion hidden={true} tagName="span">
<span>{announcement}</span>

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: is the span needed?

}

// The multi-sort priority badge renders alongside the sort arrow inside the
// absolutely-positioned sort icon. Reserve extra inline-end space for it so it

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'm OK with not including this in this scope but it would be nice if the sort icon (and the newly added priority badge) is not rendered with absolute position

.header-cell-main-with-menu {
display: flex;
align-items: center;
// stylelint-disable-next-line no-descending-specificity

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 avoid overriding our own linting rules, e.g, by adding classnames to the inner element (.header-cell-content in this case) instead of relying on the DOM structure? I think this is what we do elsewhere

}

.header-cell-main {
/* When the wrapper is present without a sort menu, behave as if it weren't there. */

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.

display: contents;
}

.sort-priority-badge {

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.

Flex-related styles are not needed if there is only one child. Same for the ruleset below

];

return (
<span className={styles['sort-menu']} onClick={e => e.stopPropagation()}>

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 the stopPropagation be handled by the button dropdown instead? Is this wrapper necessary?

@jperals

jperals commented Jul 22, 2026

Copy link
Copy Markdown
Member

Can we test the feature alongside enableKeyboardNavigation?

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