Skip to content

fix(datatable): improve sortable header accessibility#4340

Closed
edschema wants to merge 9 commits into
openedx:release-23.xfrom
Schema-Education:edschema/datatable-sort-header-a11y
Closed

fix(datatable): improve sortable header accessibility#4340
edschema wants to merge 9 commits into
openedx:release-23.xfrom
Schema-Education:edschema/datatable-sort-header-a11y

Conversation

@edschema

@edschema edschema commented Jun 18, 2026

Copy link
Copy Markdown

Note: this draft PR was closed and replaced with a more tightly scoped #4345. That implementation removes the React key cleanup.

Description

This PR improves DataTable sortable header accessibility without changing the public DataTable API.

It addresses three header behavior gaps:

  • Sort state: sortable headers had visual affordances, but active sort state was not exposed programmatically. Sorted headers now set aria-sort="ascending" or aria-sort="descending"; inactive/unsorted headers omit aria-sort.
  • Keyboard operation: sort behavior was attached to the <th>, so sortable headers were not reachable by Tab. Sortable headers now render a native button inside the existing <th>, with React Table sort toggle props on the button for native Tab, Enter, and Space behavior.
  • Column scope: DataTable header cells did not explicitly declare column scope. TableHeaderCell now renders scope="col".

Supporting changes:

  • Add scoped DataTable header-content, sortable-header, and sort-button classes so the new native button matches the previous header layout and hit target without Bootstrap utility classes or token overrides.
  • Add focused tests for sort state, header scope, sortable button rendering, toggle prop placement, Tab focus, Enter activation, Space activation, real DataTable sorting through the sortable header button, and header-path key handling.

Opportunistic change:

  • Clean up React key handling in the DataTable header

In Scope

  • Expose active sort state with aria-sort on the sorted header cell.
  • Make sortable headers keyboard reachable and operable with native header buttons.
  • Make DataTable header scope explicit with scope="col".

Out of Scope / Future Work

  • Table caption, table identity, or shared sortable-header instruction API.
  • Pre-existing React defaultProps warnings and DataTable row/cell key-spread warnings. The header path touched by this PR is cleaned up; remaining warnings existed before this a11y change and should be handled separately.

Reviewer Notes / Decisions

  • aria-sort is intentionally set only on the active sorted header cell. Inactive sortable headers omit aria-sort, following W3C/APG guidance that the attribute is set on the currently sorted column and moved when sorting changes.
  • Sortable headers use a native button instead of adding keyboard handling to the <th>. This follows the APG sortable-table pattern and keeps keyboard behavior native.
  • Non-sortable headers remain not buttons.
  • scope="col" is applied to all TableHeaderCell <th> elements because this component owns DataTable column-header markup.
  • React Table header prop getters may return React key values. This PR strips those keys out before spreading header prop objects in the touched header path. The list keys are applied at the header group/header cell map boundaries; remaining row/cell key-spread warnings are separate existing DataTable cleanup.

Verification

  • npm test -- --runTestsByPath src/DataTable/tests/TableHeaderCell.test.jsx --coverage=false passed.
  • npm test -- --runTestsByPath src/DataTable/tests/Table.test.jsx --coverage=false passed.
  • npm test -- --runTestsByPath src/DataTable/tests/DataTable.test.jsx --coverage=false passed.
  • npm test -- --runTestsByPath src/DataTable/tests/TableHeaderRow.test.jsx --coverage=false passed.
  • npm test -- --runTestsByPath src/DataTable/tests/TableHeaderCell.test.jsx src/DataTable/tests/DataTable.test.jsx --coverage=false passed.
  • npm test -- --runTestsByPath src/DataTable/tests/TableHeaderRow.test.jsx src/DataTable/tests/TableHeaderCell.test.jsx src/DataTable/tests/DataTable.test.jsx --coverage=false passed.
  • npm run lint passed.
  • git diff --check passed.
  • Local Gatsby docs page verification passed at /components/datatable/:
    • Visual: header layout, sort icons, alignment, and focus treatment remained acceptable.
    • Keyboard: Tab reached sortable header buttons; Enter and Space toggled sorting; non-sortable headers were skipped.
    • DOM: scope="col" appeared on <th>; active sorted headers exposed aria-sort; unsorted headers omitted aria-sort; sortable headers rendered button type="button".

Deploy Preview

https://deploy-preview-4340--paragon-openedx-v23.netlify.app/components/datatable/

Merge Checklist

  • If your update includes visual changes, have they been reviewed by a designer? Send them a link to the Netlify deploy preview, if applicable.
    • No intended visual change; local docs visual check passed. Netlify deploy preview review is pending.
  • Does your change adhere to the documented style conventions?
    • Added scoped pgn__ DataTable classes under the existing .pgn__data-table styles; the new CSS does not reference primitive tokens and uses inheritance for button font, color, and text alignment.
  • Do any prop types have missing descriptions in the Props API tables in the documentation site (check deploy preview)?
    • No public props or API surface were added.
  • Were your changes tested using all available themes (see theme switcher in the header of the deploy preview, under the "Settings" icon)?
    • Local docs verification passed; all-theme Netlify preview check is pending.
  • Were your changes tested in the example app?
    • Not run. DataTable docs were used for local visual, keyboard, and DOM verification.
  • Is there adequate test coverage for your changes?
    • Focused TableHeaderCell tests were added/updated and targeted DataTable tests passed.
  • Consider whether this change needs to reviewed/QA'ed for accessibility (a11y). If so, please request an a11y review for the PR in the #wg-paragon Open edX Slack channel.
    • A11y review is recommended because this changes table header semantics and keyboard behavior.

Post-merge Checklist

  • Verify your changes were released to NPM at the expected version.
  • If you'd like, share your contribution in #show-and-tell.
  • Celebrate! Thanks for your contribution.

AI Disclosure

Codex was used to help diagnose the issue, catalog the current behavior, suggest an implementation shape, execute the fix, and generate the framework for the PR text based on Open edX guidances in the docs. Each step required manual review.

@netlify

netlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Deploy Preview for paragon-openedx-v23 ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit c2cc03c
🔍 Latest deploy log https://app.netlify.com/projects/paragon-openedx-v23/deploys/6a35437e2d5f710008c3c007
😎 Deploy Preview https://deploy-preview-4340--paragon-openedx-v23.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Jun 18, 2026
@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @edschema!

This repository is currently maintained by @openedx/committers-paragon.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

🔘 Update the status of your PR

Your PR is currently marked as a draft. After completing the steps above, update its status by clicking "Ready for Review", or removing "WIP" from the title, as appropriate.


Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@codecov

codecov Bot commented Jun 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.42%. Comparing base (9340b9c) to head (c2cc03c).
⚠️ Report is 5 commits behind head on release-23.x.

Additional details and impacted files
@@               Coverage Diff                @@
##           release-23.x    #4340      +/-   ##
================================================
+ Coverage         94.40%   94.42%   +0.01%     
================================================
  Files               242      242              
  Lines              4309     4320      +11     
  Branches            981     1025      +44     
================================================
+ Hits               4068     4079      +11     
+ Misses              237      233       -4     
- Partials              4        8       +4     

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

edschema and others added 8 commits June 18, 2026 18:06
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
@edschema edschema force-pushed the edschema/datatable-sort-header-a11y branch from c87ad1a to 24e6fa4 Compare June 18, 2026 22:06
Co-authored-by: Codex <codex@openai.com>
@edschema

Copy link
Copy Markdown
Author

Superseded by #4345, which rebuilds the same DataTable sortable-header accessibility fix on a clean branch with the React key-prop cleanup left out of scope for separate maintenance work.

@edschema edschema closed this Jun 22, 2026
@github-project-automation github-project-automation Bot moved this from Needs Triage to Done in Contributions Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

open-source-contribution PR author is not from Axim or 2U

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants