Repository navigation
Conversation
`Table.HeaderCell` and `Table.SortableCell` render a `th` with no `scope`. Browsers infer header semantics from context, but tools that map roles from markup alone (for example the Vitest browser mode role locator) read a scope-less `th` as a plain cell. A cell in `Table.HeaderRow` now defaults to `scope="col"`, and a cell in `Table.Row` to `scope="row"`. An explicit `scope` prop (or `cellProps.scope` for `SortableCell`) still wins, a `th` outside a Garden row gets no scope, and each `Table` resets the context so a nested table does not inherit its host row's default. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A `th` in a body row is not always a row header: a `tbody` row of header cells heads the columns below it, and HTML's automatic header assignment already resolves both layouts. Forcing `scope="row"` there would regress such tables, so only `Table.HeaderRow` sets a default (`col`); header cells elsewhere keep no scope. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A table nested in a header row's cell (a native `table` or a Garden `Table` inside a native cell) inherited the header row context, so its header cells got `scope="col"`. `Table.Cell` and `Table.HeaderCell` now reset the context around their content. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Wrapping a cell's children in the context provider gave the `td`/`th` a child even when none was passed, so `dangerouslySetInnerHTML` threw. Put the provider around the cell element instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bbrzoska
marked this pull request as draft
September 30, 2026 19:15
Member
Author
|
Closing this. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Table.HeaderCellandTable.SortableCellinsideTable.HeaderRownow default toscope="col". An explicitscope(orcellProps.scopeonSortableCell) still wins. Header cells in body rows and plain rows render as they do today. "Behavior changes to review" below lists two other cases where output changes.Detail
Both components render a
thwith noscope. That is valid for a simple table (WCAG H63), and browsers infer the role from context: for the README example, Chrome's accessibility tree showscolumnheader. Tools that map roles from markup alone do not. Vitest browser mode'sgetByRoleuses a bundled role engine (ivya) that returnscellfor athunless it hasscope="col"orscope="row", sogetByRole('columnheader')finds nothing (vitest-dev/vitest#8054, closed with "usescope"). Consumers currently addscope="col"to every header cell by hand. The default targets the usual layout, where aTable.HeaderRowholds column headers.scopeTable.HeaderRowcolTable.Row,Table.GroupRow, a plaintrscopepropBody rows get no default on purpose. A
ththere may head its row, or, in atbodyrow made of header cells, the columns below it. HTML's automatic header assignment already handles both layouts, and forcingrowwould break the second one.Implementation:
HeaderRowprovides a booleanHeaderRowContext.Cell,HeaderCell, andTablereset it for everything they render (the provider wraps the cell element, sodangerouslySetInnerHTMLstill works). A table nested in a Garden cell of a header row therefore does not inherit the default.scope={undefined}counts as not given, so wrappers that forward an optionalscopekeep the default.Tests were written first; the header-row default tests failed before the fix. They cover the default, explicit
scope,scope={undefined}, no scope in body rows (a row of column headers intbody, a row header, a plaintr), no scope inside cell content (native and Garden nested tables),dangerouslySetInnerHTMLonCellandHeaderCell, and theSortableCelldefault, body-row, andcellProps.scopecases.npm test -- packages/tablespasses (47 tests), andeslint,prettier --check, andtscare clean.Behavior changes to review
Mixed header rows. By HTML's header assignment, a scope-less
thwhose row contains a data cell is not a column header. So:Table.HeaderRowwith aTable.Cellin it (for example a select-all checkbox): under the HTML algorithm, no header in that row is a column header today (browser heuristics may differ). The default makes them column headers.theadwhose secondTable.HeaderRowpairs a labelthwithtdvalues (the label is a row header today) now needsscope="row"on that label.Maintainers: if the second case counts as breaking for v9, I can make the default opt-in instead (for example a
Tableprop). Let me know which you prefer.Known limit: the context resets only at Garden cells and tables. A
Table.HeaderCellorTable.SortableCellinside a nativetableinside a nativetdof aTable.HeaderRowstill getscol. Passscopeexplicitly there.Checklist
👌 design updates will be Garden Designer approved (add the designer as a reviewer)🌐 demo is up-to-date (npm start)⬅️ renders as expected with reversed (RTL) direction⚫ renders as expected in dark mode🤘 renders as expected with Bedrock CSS (?bedrock)🤖 Generated with Claude Code