Skip to content

fix: AppTable remounting children on table state updates#6331

Merged
KevinVandy merged 4 commits into
TanStack:betafrom
Abyanzhafran:fix/6323
Jun 17, 2026
Merged

fix: AppTable remounting children on table state updates#6331
KevinVandy merged 4 commits into
TanStack:betafrom
Abyanzhafran:fix/6323

Conversation

@Abyanzhafran

@Abyanzhafran Abyanzhafran commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

App wrappers (AppTable, AppCell, AppHeader, AppFooter) were memoized on the unstable table reference, causing them to be recreated every render. React then remounted their entire subtrees on every state change, destroying controlled input focus.

Keep wrappers stable (created once) and read the current table from a ref updated each render. Fixes children remounting while preserving latest table state access.

Fixes : #6331

🎯 Changes

RCA: AppTable remounting children on every table state update

Problem

table.AppTable (and AppCell, AppHeader, AppFooter) created a new function reference on every render. React uses function identity to decide if a component should re-render or remount. A new function = unmount + remount the entire subtree, destroying controlled input focus.

Root Cause

  1. useTable returns a fresh table reference every render (by design, for React Compiler compatibility)
  2. useAppTable feeds a new options object literal to useTable every render
  3. The four App wrappers were memoized with [table] as a dependency
  4. Since [table] changes every render, the wrappers were recreated every render
  5. React saw a new component type → unmounted children instead of re-rendering them
  6. A controlled input in a toolbar lost focus on every keystroke

Solution

Decouple component identity (must be stable) from the table reference the component reads (must be current) using a ref pattern:

  1. Import useRef
  2. Create a ref that tracks the current table:
    const tableRef = useRef(table)
    tableRef.current = table
    

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

Summary by CodeRabbit

  • Bug Fixes

    • Improved stability of table wrapper components to prevent unnecessary recreation/remounts during frequent table updates by using a stable internal table reference.
  • New Features

    • Added row-selection “select” columns across React, Preact, Angular, Solid, Svelte, Vue, and Lit examples, including synchronized header/row checkboxes with indeterminate (some selected) behavior.
    • Introduced reusable indeterminate checkbox components in the respective example frameworks.
    • Enhanced example table layout with a fixed-height scroll container and sticky table headers.

App wrappers (AppTable, AppCell, AppHeader, AppFooter) were memoized
on the unstable `table` reference, causing them to be recreated every
render. React then remounted their entire subtrees on every state
change, destroying controlled input focus.

Keep wrappers stable (created once) and read the current table from a
ref updated each render. Fixes children remounting while preserving
latest table state access.
@Abyanzhafran Abyanzhafran changed the title fix: AppTable remounting children on table state updates (#6323) fix: AppTable remounting children on table state updates Jun 17, 2026
@KevinVandy

Copy link
Copy Markdown
Member

Awesome, I'll try this out soon

@nx-cloud

nx-cloud Bot commented Jun 17, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 9b6daf1

Command Status Duration Result
nx affected --targets=test:eslint,test:sherif,t... ✅ Succeeded 4m 6s View ↗
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 1s View ↗

☁️ Nx Cloud last updated this comment at 2026-06-17 14:19:06 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/angular-table@6331

@tanstack/angular-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/angular-table-devtools@6331

@tanstack/lit-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/lit-table@6331

@tanstack/match-sorter-utils

npm i https://pkg.pr.new/TanStack/table/@tanstack/match-sorter-utils@6331

@tanstack/preact-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/preact-table@6331

@tanstack/preact-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/preact-table-devtools@6331

@tanstack/react-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/react-table@6331

@tanstack/react-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/react-table-devtools@6331

@tanstack/solid-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/solid-table@6331

@tanstack/solid-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/solid-table-devtools@6331

@tanstack/svelte-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/svelte-table@6331

@tanstack/table-core

npm i https://pkg.pr.new/TanStack/table/@tanstack/table-core@6331

@tanstack/table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/table-devtools@6331

@tanstack/vue-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/vue-table@6331

@tanstack/vue-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/vue-table-devtools@6331

commit: b02f793

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Both packages/react-table and packages/preact-table introduce a tableRef inside useAppTable. The table returned by useTable is assigned to tableRef.current on every render. All four App wrapper components (AppTable, AppCell, AppHeader, AppFooter) now read currentTable = tableRef.current at render time instead of closing over table, and their useMemo dependency arrays change from [table] to []. Across seven example frameworks, row selection is added using new IndeterminateCheckbox and SelectCell/SelectHeader components, integrating rowSelectionFeature and enabling scrollable tables with sticky headers for both Users and Products tables.

Changes

Core stable App wrapper ref pattern

Layer / File(s) Summary
tableRef initialization in useAppTable
packages/react-table/src/createTableHook.tsx, packages/preact-table/src/createTableHook.tsx
useRef is added to imports; tableRef is created in useAppTable and assigned tableRef.current = table on each render in both packages, decoupling wrapper component identity from the per-render table reference.
AppTable, AppCell, AppHeader, AppFooter updated to read from ref
packages/react-table/src/createTableHook.tsx, packages/preact-table/src/createTableHook.tsx
All four wrapper components in both packages now derive currentTable from tableRef.current inside their memoized render functions, supply currentTable to TableContext/CellContext/HeaderContext, route Subscribe calls through currentTable.Subscribe, and change their useMemo dependency arrays from [table] to [].

Row selection feature for all example frameworks

Layer / File(s) Summary
IndeterminateCheckbox component
examples/react/composable-tables/src/components/indeterminate-checkbox.tsx, examples/preact/composable-tables/src/components/indeterminate-checkbox.tsx, examples/solid/composable-tables/src/components/indeterminate-checkbox.tsx, examples/vue/composable-tables/src/components/IndeterminateCheckbox.vue
New reusable checkbox component across frameworks that sets the native HTML input indeterminate property via useEffect/createEffect/watchEffect and ref, supporting checked, disabled, onChange, and className props with passthrough of additional attributes.
SelectCell and SelectHeader components
examples/react/composable-tables/src/components/cell-components.tsx, examples/preact/composable-tables/src/components/cell-components.tsx, examples/angular/composable-tables/src/app/components/cell-components.ts, examples/angular/composable-tables/src/app/components/header-components.ts, examples/svelte/composable-tables/src/components/SelectCell.svelte, examples/svelte/composable-tables/src/components/SelectHeader.svelte, examples/vue/composable-tables/src/components/cell-components.ts, examples/vue/composable-tables/src/components/header-components.ts, examples/solid/composable-tables/src/components/cell-components.tsx
New row-selection cell and header components across frameworks that use context hooks to read table state and row/header APIs, rendering checkboxes wired to selection state and handlers (getIsSelected, getCanSelect, getIsSomeSelected, getToggleSelectedHandler, getIsAllRowsSelected).
rowSelectionFeature integration in example hooks
examples/react/composable-tables/src/hooks/table.ts, examples/preact/composable-tables/src/hooks/table.ts, examples/angular/composable-tables/src/app/table.ts, examples/lit/composable-tables/src/hooks/table.ts, examples/solid/composable-tables/src/hooks/table.ts, examples/svelte/composable-tables/src/hooks/table.ts, examples/vue/composable-tables/src/hooks/table.ts
All example hooks import rowSelectionFeature, add it to tableFeatures configuration, import SelectCell/SelectHeader components, and register them in cellComponents and headerComponents mappings so they are available as named cell/header components.
Scrollable table container styling
examples/react/composable-tables/src/index.css, examples/preact/composable-tables/src/index.css, examples/angular/composable-tables/src/styles.css, examples/lit/composable-tables/src/index.css, examples/solid/composable-tables/src/index.css, examples/svelte/composable-tables/src/index.css, examples/vue/composable-tables/src/index.css
CSS rules define .table-scroll wrapper with fixed max-height and overflow: auto, and sticky positioning for thead th to keep headers visible during vertical scroll across all example frameworks.
React and Preact example apps: select columns and scrollable tables
examples/react/composable-tables/src/main.tsx, examples/preact/composable-tables/src/main.tsx
Example main files add Subscribe and IndeterminateCheckbox imports, introduce select display columns for Users and Products tables with header checkboxes tied to table.atoms.rowSelection and cells using cell.SelectCell, set enableRowSelection: true in table options, and wrap tables in div.table-scroll containers while preserving sort/filter/footer logic.
Angular example app: select columns and scrollable tables
examples/angular/composable-tables/src/app/components/products-table/products-table.ts, examples/angular/composable-tables/src/app/components/products-table/products-table.html, examples/angular/composable-tables/src/app/components/users-table/users-table.ts, examples/angular/composable-tables/src/app/components/users-table/users-table.html
Table components add select display columns with flexRenderComponent wiring to SelectHeader and SelectCell, enable enableRowSelection: true, and wrap tables in table-scroll containers while preserving header/body/footer rendering logic.
Lit example app: select columns and scrollable tables
examples/lit/composable-tables/src/components/products-table.ts, examples/lit/composable-tables/src/components/users-table.ts
Table components add select display columns with header and row checkboxes wired to selection handlers, enable row selection and wire rowSelection state, and refactor markup to include scroll wrapper with adjusted header/body/footer rendering (treating select column like actions column with no footer content).
Solid example app: select columns and scrollable tables
examples/solid/composable-tables/src/App.tsx
App component adds select display columns with IndeterminateCheckbox headers wired to table selection state and cell.SelectCell for rows in both Users and Products tables, enables enableRowSelection: true, and wraps tables in table-scroll containers while preserving sort/filter/footer rendering.
Svelte example app: select columns and scrollable tables
examples/svelte/composable-tables/src/components/ProductsTable.svelte, examples/svelte/composable-tables/src/components/UsersTable.svelte
Example components add select display columns wired to SelectHeader and SelectCell, enable enableRowSelection: true, and wrap tables in table-scroll containers with corresponding closing divs to support scrollable layout.
Vue example app: select columns and scrollable tables
examples/vue/composable-tables/src/components/ProductsTable.vue, examples/vue/composable-tables/src/components/UsersTable.vue
Example components add select display columns with SelectAllHeader and SelectCell wired to table selection APIs, enable enableRowSelection: true, and wrap tables in table-scroll containers while restructuring template layout to preserve header/body/footer rendering logic.

Sequence Diagram(s)

sequenceDiagram
  participant SelectCell
  participant Context as useCellContext<br/>useTableContext
  participant Subscribe
  participant Atoms as table.atoms<br/>.rowSelection
  participant Checkbox as IndeterminateCheckbox

  SelectCell->>Context: read current cell/row
  SelectCell->>Context: read table reference
  SelectCell->>Subscribe: wrap render with subscription
  Subscribe->>Atoms: observe row selection state
  Atoms-->>Subscribe: emit updates on change
  Subscribe->>Checkbox: render with row.getIsSelected()
  Checkbox->>SelectCell: user toggles → row.getToggleSelectedHandler()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Suggested reviewers

  • KevinVandy

Poem

🐇 A ref held steady through every render's flight,
Selections now checked, both left and right.
The wrappers stay stable, deps set to [],
Subscribe brings updates in scrollable cells,
Seven frameworks hop together—the table works well! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main fix: stabilizing AppTable wrapper components to prevent remounting on table state updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/preact/composable-tables/src/components/cell-components.tsx`:
- Around line 27-32: The IndeterminateCheckbox component used for row selection
in the cell-components.tsx file is missing an accessible name, making it
inaccessible to assistive technologies. Add an aria-label attribute to the
IndeterminateCheckbox component that provides a descriptive name for the
checkbox, such as indicating that it selects the current row. This will ensure
screen readers announce the control's purpose to users.

In `@examples/preact/composable-tables/src/main.tsx`:
- Around line 39-55: The IndeterminateCheckbox component in the select-column
header (within personColumnHelper.display with id 'select') lacks an accessible
name for assistive technology. Add an aria-label prop to the
IndeterminateCheckbox to provide a descriptive label such as "Select all rows".
Apply the same fix to the analogous Products select-column header around lines
271-287 to ensure both select-all checkboxes are properly labeled for
accessibility.

In `@examples/react/composable-tables/src/components/cell-components.tsx`:
- Around line 27-32: The IndeterminateCheckbox component used for row selection
is missing an accessible name, making it inaccessible to assistive technologies.
Add an aria-label prop to the IndeterminateCheckbox component that clearly
describes its purpose, such as "Select row" or a more descriptive label that
indicates which row is being selected. This will ensure screen readers announce
the checkbox with meaningful context to users.

In `@examples/react/composable-tables/src/main.tsx`:
- Around line 40-56: The IndeterminateCheckbox components used in both the
select-column headers (in the personColumnHelper.display for Users and the
corresponding Products column helper) are missing accessible names, making them
unnamed to assistive technology. Add an aria-label prop to each
IndeterminateCheckbox instance that clearly describes its purpose, such as
"Select all rows" or "Toggle all rows selection", to ensure the checkboxes are
properly labeled for screen readers and other assistive technologies.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6be50bba-3190-496f-84ee-5b14299ba124

📥 Commits

Reviewing files that changed from the base of the PR and between 214695c and 9b6daf1.

📒 Files selected for processing (10)
  • examples/preact/composable-tables/src/components/cell-components.tsx
  • examples/preact/composable-tables/src/components/indeterminate-checkbox.tsx
  • examples/preact/composable-tables/src/hooks/table.ts
  • examples/preact/composable-tables/src/index.css
  • examples/preact/composable-tables/src/main.tsx
  • examples/react/composable-tables/src/components/cell-components.tsx
  • examples/react/composable-tables/src/components/indeterminate-checkbox.tsx
  • examples/react/composable-tables/src/hooks/table.ts
  • examples/react/composable-tables/src/index.css
  • examples/react/composable-tables/src/main.tsx
✅ Files skipped from review due to trivial changes (2)
  • examples/react/composable-tables/src/index.css
  • examples/react/composable-tables/src/components/indeterminate-checkbox.tsx

Comment on lines +27 to +32
<IndeterminateCheckbox
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible name to row selection checkboxes.

The row checkbox is rendered without a label/aria-label, so assistive technologies get an unnamed control.

Suggested patch
         <IndeterminateCheckbox
+          aria-label={`Select row ${row.id}`}
           checked={row.getIsSelected()}
           disabled={!row.getCanSelect()}
           indeterminate={row.getIsSomeSelected()}
           onChange={row.getToggleSelectedHandler()}
         />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<IndeterminateCheckbox
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
<IndeterminateCheckbox
aria-label={`Select row ${row.id}`}
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/preact/composable-tables/src/components/cell-components.tsx` around
lines 27 - 32, The IndeterminateCheckbox component used for row selection in the
cell-components.tsx file is missing an accessible name, making it inaccessible
to assistive technologies. Add an aria-label attribute to the
IndeterminateCheckbox component that provides a descriptive name for the
checkbox, such as indicating that it selects the current row. This will ensure
screen readers announce the control's purpose to users.

Comment on lines +39 to +55
personColumnHelper.display({
id: 'select',
header: ({ table }) => (
// Subscribe keeps the select-all checkbox in sync with selection state (see SelectCell)
<Subscribe source={table.atoms.rowSelection}>
{() => (
<IndeterminateCheckbox
checked={table.getIsAllRowsSelected()}
indeterminate={table.getIsSomeRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>
)}
</Subscribe>
),
// Cell uses the pre-bound SelectCell component via AppCell
cell: ({ cell }) => <cell.SelectCell />,
}),

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible name to the select-all checkbox.

Both header-level selection checkboxes are missing a label/aria-label, which leaves the control unnamed in assistive tech.

Suggested patch
                 <IndeterminateCheckbox
+                  aria-label="Select all rows"
                   checked={table.getIsAllRowsSelected()}
                   indeterminate={table.getIsSomeRowsSelected()}
                   onChange={table.getToggleAllRowsSelectedHandler()}
                 />

Apply the same addition to both Users and Products select-column headers.

Also applies to: 271-287

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/preact/composable-tables/src/main.tsx` around lines 39 - 55, The
IndeterminateCheckbox component in the select-column header (within
personColumnHelper.display with id 'select') lacks an accessible name for
assistive technology. Add an aria-label prop to the IndeterminateCheckbox to
provide a descriptive label such as "Select all rows". Apply the same fix to the
analogous Products select-column header around lines 271-287 to ensure both
select-all checkboxes are properly labeled for accessibility.

Comment on lines +27 to +32
<IndeterminateCheckbox
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible name to row selection checkboxes.

The row checkbox is rendered without a label/aria-label, so assistive technologies get an unnamed control.

Suggested patch
         <IndeterminateCheckbox
+          aria-label={`Select row ${row.id}`}
           checked={row.getIsSelected()}
           disabled={!row.getCanSelect()}
           indeterminate={row.getIsSomeSelected()}
           onChange={row.getToggleSelectedHandler()}
         />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<IndeterminateCheckbox
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
<IndeterminateCheckbox
aria-label={`Select row ${row.id}`}
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/react/composable-tables/src/components/cell-components.tsx` around
lines 27 - 32, The IndeterminateCheckbox component used for row selection is
missing an accessible name, making it inaccessible to assistive technologies.
Add an aria-label prop to the IndeterminateCheckbox component that clearly
describes its purpose, such as "Select row" or a more descriptive label that
indicates which row is being selected. This will ensure screen readers announce
the checkbox with meaningful context to users.

Comment on lines +40 to +56
personColumnHelper.display({
id: 'select',
header: ({ table }) => (
// Subscribe works around React Compiler memoization (see SelectCell)
<Subscribe source={table.atoms.rowSelection}>
{() => (
<IndeterminateCheckbox
checked={table.getIsAllRowsSelected()}
indeterminate={table.getIsSomeRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>
)}
</Subscribe>
),
// Cell uses the pre-bound SelectCell component via AppCell
cell: ({ cell }) => <cell.SelectCell />,
}),

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible name to the select-all checkbox.

Both header-level selection checkboxes are missing a label/aria-label, which leaves the control unnamed in assistive tech.

Suggested patch
                 <IndeterminateCheckbox
+                  aria-label="Select all rows"
                   checked={table.getIsAllRowsSelected()}
                   indeterminate={table.getIsSomeRowsSelected()}
                   onChange={table.getToggleAllRowsSelectedHandler()}
                 />

Apply the same addition to both Users and Products select-column headers.

Also applies to: 268-284

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/react/composable-tables/src/main.tsx` around lines 40 - 56, The
IndeterminateCheckbox components used in both the select-column headers (in the
personColumnHelper.display for Users and the corresponding Products column
helper) are missing accessible names, making them unnamed to assistive
technology. Add an aria-label prop to each IndeterminateCheckbox instance that
clearly describes its purpose, such as "Select all rows" or "Toggle all rows
selection", to ensure the checkboxes are properly labeled for screen readers and
other assistive technologies.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/preact/composable-tables/src/main.tsx`:
- Around line 41-45: The AppTable selector is missing the rowSelection state
tracking, which causes the header checkbox components (IndeterminateCheckbox) to
not re-render when row selection changes, leaving them in a stale state. Locate
both AppTable selector definitions (one around pagination/sorting/columnFilters
configuration) and add rowSelection to the selector function by including
rowSelection: state.rowSelection alongside the existing pagination, sorting, and
columnFilters properties. This ensures the component re-executes when row
selection state changes, keeping the getIsAllRowsSelected() and
getIsSomeRowsSelected() checks current.

In `@examples/solid/composable-tables/src/App.tsx`:
- Around line 202-224: The footer rendering logic for the 'select' column
currently falls into the generic footer branch which renders FooterColumnId and
filtered indicators inappropriately for this checkbox utility column. Modify the
conditional logic to skip footer rendering for the 'select' column by updating
the condition that currently checks for columnId === 'actions' to also exclude
the 'select' column (either by adding columnId === 'select' to the existing null
return condition or by creating a separate check). Apply this same fix to all
occurrences where this footer rendering pattern appears, including the section
mentioned at lines 426-448.

In `@examples/solid/composable-tables/src/components/cell-components.tsx`:
- Around line 21-26: The IndeterminateCheckbox component used for row selection
is missing an aria-label attribute, which prevents assistive technologies from
understanding its purpose. Add an aria-label prop to the IndeterminateCheckbox
component that clearly describes its function, such as indicating that it
controls row selection. The label should be accessible and descriptive so screen
readers can properly announce the checkbox's purpose to users.

In `@examples/svelte/composable-tables/src/components/SelectCell.svelte`:
- Around line 25-31: The checkbox input element in the SelectCell.svelte
component lacks an aria-label attribute, making it inaccessible to assistive
technologies. Add an aria-label attribute to the input element (the one with
type="checkbox" that has the checked, disabled, and onchange attributes) with a
descriptive label that clearly indicates it selects the current row for screen
readers and other assistive devices.

In `@examples/svelte/composable-tables/src/components/SelectHeader.svelte`:
- Around line 24-31: Add an aria-label attribute to the checkbox input element
in the SelectHeader component to provide an accessible name for screen readers.
The checkbox currently lacks a label that assistive technologies can announce to
users. Include an aria-label with a descriptive text such as "Select all rows"
to clearly communicate the purpose of this control to users relying on assistive
technologies.

In `@examples/vue/composable-tables/src/components/header-components.ts`:
- Around line 12-16: The IndeterminateCheckbox component in SelectAllHeader is
missing an accessible name property. Add an aria-label prop to the
IndeterminateCheckbox component that clearly describes its purpose, such as
selecting or deselecting all rows in the table. This will ensure screen readers
and assistive technologies can properly announce the checkbox's function to
users.
- Around line 15-16: The SelectAllHeader checkbox click event is bubbling up to
the parent table header element and triggering the sort handler. Modify the
onChange handler of the IndeterminateCheckbox component to prevent event
propagation by wrapping the table.getToggleAllRowsSelectedHandler() call in a
handler function that calls event.stopPropagation() before invoking the toggle
handler, similar to the ColumnFilter pattern used elsewhere in this file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14acac1c-2dc0-4f95-9e6d-5e7d2a447689

📥 Commits

Reviewing files that changed from the base of the PR and between 9b6daf1 and b02f793.

📒 Files selected for processing (32)
  • examples/angular/composable-tables/src/app/components/cell-components.ts
  • examples/angular/composable-tables/src/app/components/header-components.ts
  • examples/angular/composable-tables/src/app/components/products-table/products-table.html
  • examples/angular/composable-tables/src/app/components/products-table/products-table.ts
  • examples/angular/composable-tables/src/app/components/users-table/users-table.html
  • examples/angular/composable-tables/src/app/components/users-table/users-table.ts
  • examples/angular/composable-tables/src/app/table.ts
  • examples/angular/composable-tables/src/styles.css
  • examples/lit/composable-tables/src/components/products-table.ts
  • examples/lit/composable-tables/src/components/users-table.ts
  • examples/lit/composable-tables/src/hooks/table.ts
  • examples/lit/composable-tables/src/index.css
  • examples/preact/composable-tables/src/components/cell-components.tsx
  • examples/preact/composable-tables/src/main.tsx
  • examples/solid/composable-tables/src/App.tsx
  • examples/solid/composable-tables/src/components/cell-components.tsx
  • examples/solid/composable-tables/src/components/indeterminate-checkbox.tsx
  • examples/solid/composable-tables/src/hooks/table.ts
  • examples/solid/composable-tables/src/index.css
  • examples/svelte/composable-tables/src/components/ProductsTable.svelte
  • examples/svelte/composable-tables/src/components/SelectCell.svelte
  • examples/svelte/composable-tables/src/components/SelectHeader.svelte
  • examples/svelte/composable-tables/src/components/UsersTable.svelte
  • examples/svelte/composable-tables/src/hooks/table.ts
  • examples/svelte/composable-tables/src/index.css
  • examples/vue/composable-tables/src/components/IndeterminateCheckbox.vue
  • examples/vue/composable-tables/src/components/ProductsTable.vue
  • examples/vue/composable-tables/src/components/UsersTable.vue
  • examples/vue/composable-tables/src/components/cell-components.ts
  • examples/vue/composable-tables/src/components/header-components.ts
  • examples/vue/composable-tables/src/hooks/table.ts
  • examples/vue/composable-tables/src/index.css
✅ Files skipped from review due to trivial changes (4)
  • examples/vue/composable-tables/src/index.css
  • examples/solid/composable-tables/src/components/indeterminate-checkbox.tsx
  • examples/vue/composable-tables/src/components/IndeterminateCheckbox.vue
  • examples/angular/composable-tables/src/app/components/products-table/products-table.html

Comment on lines +41 to +45
<IndeterminateCheckbox
checked={table.getIsAllRowsSelected()}
indeterminate={table.getIsSomeRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate preact table hook implementation =="
fd -i 'createTableHook.tsx'

echo
echo "== Inspect AppTable/AppHeader subscription behavior in preact hook =="
rg -n -C4 'AppTable|AppHeader|selector|Subscribe|rowSelection|atoms\.rowSelection' packages/preact-table/src/createTableHook.tsx

echo
echo "== Inspect current file selectors and checkbox wiring =="
rg -n -C3 'AppTable|selector=\(|getIsAllRowsSelected|getIsSomeRowsSelected|getToggleAllRowsSelectedHandler|IndeterminateCheckbox' examples/preact/composable-tables/src/main.tsx

Repository: TanStack/table

Length of output: 25838


🏁 Script executed:

# Get the full AppTable selector and header rendering structure
sed -n '104,244p' examples/preact/composable-tables/src/main.tsx

Repository: TanStack/table

Length of output: 6014


🏁 Script executed:

# Also check the ProductTable section
sed -n '321,458p' examples/preact/composable-tables/src/main.tsx

Repository: TanStack/table

Length of output: 5743


🏁 Script executed:

# Check if table.Subscribe is available and how it might be used for rowSelection
rg -n 'getIsAllRowsSelected|getIsSomeRowsSelected|rowSelection.*Subscribe' examples/preact/composable-tables/src/main.tsx

Repository: TanStack/table

Length of output: 298


🏁 Script executed:

# Verify that AppHeader without selector doesn't have any Subscribe logic for rowSelection
sed -n '945,1023p' packages/preact-table/src/createTableHook.tsx

Repository: TanStack/table

Length of output: 2565


Add rowSelection to the AppTable selector to keep the header checkboxes reactive.

The checkboxes at lines 41-45 and 268-272 read table.getIsAllRowsSelected() and table.getIsSomeRowsSelected() directly within the column header function. However, the AppTable selector (lines 107-109 and 323-325) does not include rowSelection, only pagination, sorting, and columnFilters. This means when row selection changes, AppTable won't re-render and the header function won't re-execute, leaving the checkbox state stale until another subscribed state updates. Add rowSelection to both AppTable selectors:

selector={(state) => ({
  pagination: state.pagination,
  sorting: state.sorting,
  columnFilters: state.columnFilters,
  rowSelection: state.rowSelection,
})}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/preact/composable-tables/src/main.tsx` around lines 41 - 45, The
AppTable selector is missing the rowSelection state tracking, which causes the
header checkbox components (IndeterminateCheckbox) to not re-render when row
selection changes, leaving them in a stale state. Locate both AppTable selector
definitions (one around pagination/sorting/columnFilters configuration) and add
rowSelection to the selector function by including rowSelection:
state.rowSelection alongside the existing pagination, sorting, and columnFilters
properties. This ensures the component re-executes when row selection state
changes, keeping the getIsAllRowsSelected() and getIsSomeRowsSelected() checks
current.

Comment on lines +202 to +224
{columnId === 'age' ||
columnId === 'visits' ||
columnId === 'progress' ? (
<>
<footer.FooterSum />
{hasFilter() && (
<span class="filtered-indicator">
{' '}
(filtered)
</span>
)}
</>
) : columnId === 'actions' ? null : (
<>
<footer.FooterColumnId />
{hasFilter() && (
<span class="filtered-indicator">
{' '}
</span>
)}
</>
)}

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Skip footer rendering for the select display column.

The new selection column currently falls into generic footer branches, so it can render FooterColumnId/filtered indicators for a checkbox utility column (e.g., “select”). This is a small UX correctness bug.

Suggested patch
@@
-                                        ) : columnId === 'actions' ? null : (
+                                        ) : columnId === 'actions' ||
+                                          columnId === 'select' ? null : (
@@
-                                        {columnId === 'price' ||
+                                        {columnId === 'select' ? null : columnId === 'price' ||
                                         columnId === 'stock' ||
                                         columnId === 'rating' ? (

Also applies to: 426-448

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/solid/composable-tables/src/App.tsx` around lines 202 - 224, The
footer rendering logic for the 'select' column currently falls into the generic
footer branch which renders FooterColumnId and filtered indicators
inappropriately for this checkbox utility column. Modify the conditional logic
to skip footer rendering for the 'select' column by updating the condition that
currently checks for columnId === 'actions' to also exclude the 'select' column
(either by adding columnId === 'select' to the existing null return condition or
by creating a separate check). Apply this same fix to all occurrences where this
footer rendering pattern appears, including the section mentioned at lines
426-448.

Comment on lines +21 to +26
<IndeterminateCheckbox
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible name to the row selection checkbox.

The checkbox should include an aria-label so assistive technologies can identify its purpose.

Suggested patch
     <IndeterminateCheckbox
+      aria-label={`Select row ${row.id}`}
       checked={row.getIsSelected()}
       disabled={!row.getCanSelect()}
       indeterminate={row.getIsSomeSelected()}
       onChange={row.getToggleSelectedHandler()}
     />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<IndeterminateCheckbox
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
<IndeterminateCheckbox
aria-label={`Select row ${row.id}`}
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/solid/composable-tables/src/components/cell-components.tsx` around
lines 21 - 26, The IndeterminateCheckbox component used for row selection is
missing an aria-label attribute, which prevents assistive technologies from
understanding its purpose. Add an aria-label prop to the IndeterminateCheckbox
component that clearly describes its function, such as indicating that it
controls row selection. The label should be accessible and descriptive so screen
readers can properly announce the checkbox's purpose to users.

Comment on lines +25 to +31
<input
type="checkbox"
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
use:setIndeterminate={!row.getIsSelected() && row.getIsSomeSelected()}
onchange={row.getToggleSelectedHandler()}
/>

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible name to the row checkbox.

This checkbox is currently unnamed for assistive tech. Add an aria-label describing that it selects the current row.

Suggested patch
 <input
   type="checkbox"
+  aria-label={`Select row ${row.id}`}
   checked={row.getIsSelected()}
   disabled={!row.getCanSelect()}
   use:setIndeterminate={!row.getIsSelected() && row.getIsSomeSelected()}
   onchange={row.getToggleSelectedHandler()}
 />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<input
type="checkbox"
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
use:setIndeterminate={!row.getIsSelected() && row.getIsSomeSelected()}
onchange={row.getToggleSelectedHandler()}
/>
<input
type="checkbox"
aria-label={`Select row ${row.id}`}
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
use:setIndeterminate={!row.getIsSelected() && row.getIsSomeSelected()}
onchange={row.getToggleSelectedHandler()}
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/svelte/composable-tables/src/components/SelectCell.svelte` around
lines 25 - 31, The checkbox input element in the SelectCell.svelte component
lacks an aria-label attribute, making it inaccessible to assistive technologies.
Add an aria-label attribute to the input element (the one with type="checkbox"
that has the checked, disabled, and onchange attributes) with a descriptive
label that clearly indicates it selects the current row for screen readers and
other assistive devices.

Comment on lines +24 to +31
<input
type="checkbox"
checked={table.getIsAllRowsSelected()}
use:setIndeterminate={!table.getIsAllRowsSelected() &&
table.getIsSomeRowsSelected()}
onchange={table.getToggleAllRowsSelectedHandler()}
onclick={(e) => e.stopPropagation()}
/>

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible name to the select-all checkbox.

The header checkbox should expose a clear accessible name (e.g., “Select all rows”).

Suggested patch
 <input
   type="checkbox"
+  aria-label="Select all rows"
   checked={table.getIsAllRowsSelected()}
   use:setIndeterminate={!table.getIsAllRowsSelected() &&
     table.getIsSomeRowsSelected()}
   onchange={table.getToggleAllRowsSelectedHandler()}
   onclick={(e) => e.stopPropagation()}
 />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<input
type="checkbox"
checked={table.getIsAllRowsSelected()}
use:setIndeterminate={!table.getIsAllRowsSelected() &&
table.getIsSomeRowsSelected()}
onchange={table.getToggleAllRowsSelectedHandler()}
onclick={(e) => e.stopPropagation()}
/>
<input
type="checkbox"
aria-label="Select all rows"
checked={table.getIsAllRowsSelected()}
use:setIndeterminate={!table.getIsAllRowsSelected() &&
table.getIsSomeRowsSelected()}
onchange={table.getToggleAllRowsSelectedHandler()}
onclick={(e) => e.stopPropagation()}
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/svelte/composable-tables/src/components/SelectHeader.svelte` around
lines 24 - 31, Add an aria-label attribute to the checkbox input element in the
SelectHeader component to provide an accessible name for screen readers. The
checkbox currently lacks a label that assistive technologies can announce to
users. Include an aria-label with a descriptive text such as "Select all rows"
to clearly communicate the purpose of this control to users relying on assistive
technologies.

Comment on lines +12 to +16
return h(IndeterminateCheckbox, {
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler(),
})

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Pass an accessible name to the select-all checkbox component.

SelectAllHeader should provide an explicit accessible name to the rendered checkbox.

Suggested patch
       return h(IndeterminateCheckbox, {
+        'aria-label': 'Select all rows',
         checked: table.getIsAllRowsSelected(),
         indeterminate: table.getIsSomeRowsSelected(),
         onChange: table.getToggleAllRowsSelectedHandler(),
       })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return h(IndeterminateCheckbox, {
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler(),
})
return h(IndeterminateCheckbox, {
'aria-label': 'Select all rows',
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler(),
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/vue/composable-tables/src/components/header-components.ts` around
lines 12 - 16, The IndeterminateCheckbox component in SelectAllHeader is missing
an accessible name property. Add an aria-label prop to the IndeterminateCheckbox
component that clearly describes its purpose, such as selecting or deselecting
all rows in the table. This will ensure screen readers and assistive
technologies can properly announce the checkbox's function to users.

Comment on lines +15 to +16
onChange: table.getToggleAllRowsSelectedHandler(),
})

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Inspect Vue table header click wiring:"
fd -i 'ProductsTable.vue' examples/vue/composable-tables/src/components --exec rg -n -C2 '`@click`|onClick|getToggleSortingHandler|toggleSorting'
fd -i 'UsersTable.vue' examples/vue/composable-tables/src/components --exec rg -n -C2 '`@click`|onClick|getToggleSortingHandler|toggleSorting'

echo
echo "Inspect SelectAllHeader implementation:"
rg -n -C3 'SelectAllHeader|stopPropagation|getToggleAllRowsSelectedHandler' examples/vue/composable-tables/src/components/header-components.ts

Repository: TanStack/table

Length of output: 1476


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Locate and examine IndeterminateCheckbox.vue ==="
fd -i 'IndeterminateCheckbox.vue' examples/vue/composable-tables/src --exec cat -n {}

echo
echo "=== Check how SelectAllHeader is used in table headers ==="
rg -n 'SelectAllHeader' examples/vue/composable-tables/src/components --type vue --type ts -B2 -A2

echo
echo "=== Look at full header-components.ts file ==="
wc -l examples/vue/composable-tables/src/components/header-components.ts

Repository: TanStack/table

Length of output: 970


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for SelectAllHeader usage ==="
rg -n 'SelectAllHeader' examples/vue/composable-tables/src/components -g '*.vue' -g '*.ts' -B3 -A3

echo
echo "=== Full header-components.ts file ==="
cat -n examples/vue/composable-tables/src/components/header-components.ts

Repository: TanStack/table

Length of output: 5431


Add event propagation prevention to SelectAllHeader checkbox.

Clicking the checkbox will bubble the click event to the parent <th> element and trigger the sort handler in ProductsTable.vue and UsersTable.vue. The IndeterminateCheckbox needs to prevent this propagation. Either wrap it with event.stopPropagation() (following the ColumnFilter pattern in this file), or add the handler directly to the checkbox input:

Example fix
return h(IndeterminateCheckbox, {
  checked: table.getIsAllRowsSelected(),
  indeterminate: table.getIsSomeRowsSelected(),
  onChange: (event: Event) => {
    event.stopPropagation()
    table.getToggleAllRowsSelectedHandler()(event)
  },
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/vue/composable-tables/src/components/header-components.ts` around
lines 15 - 16, The SelectAllHeader checkbox click event is bubbling up to the
parent table header element and triggering the sort handler. Modify the onChange
handler of the IndeterminateCheckbox component to prevent event propagation by
wrapping the table.getToggleAllRowsSelectedHandler() call in a handler function
that calls event.stopPropagation() before invoking the toggle handler, similar
to the ColumnFilter pattern used elsewhere in this file.

@KevinVandy KevinVandy merged commit ef666b2 into TanStack:beta Jun 17, 2026
8 checks passed
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