Skip to content

fix: stabilize resizable tables in nested flex layouts - #10715

Open
minwookshin wants to merge 4 commits into
adobe:mainfrom
minwookshin:fix/resizable-table-flex-growth
Open

minwookshin wants to merge 4 commits into
adobe:mainfrom
minwookshin:fix/resizable-table-flex-growth

Conversation

@minwookshin

@minwookshin minwookshin commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Closes #5412.

Resizable tables should settle within their available width inside nested flex containers. The scroll viewport's clientWidth becomes the column-width budget, but a native table also renders padding, spacing and borders beyond that budget. When its flex ancestor grows to accommodate that extra space, the next measurement can allocate the extra space to columns again.

The container now subtracts the difference between the table's rendered border box and the widths allocated by the resize state. This preserves existing column box-sizing instead of overriding it and avoids separate formulas for table borders, spacing and cell padding. Column-width changes trigger a measurement, including collection changes whose total width stays the same. A single ResizeObserver local to the container observes both the table and scroll viewport, keeping the shared observer hook unchanged. Its entries provide fractional, untransformed border-box measurements. The correction checks computed display and applies only to intrinsic min-content tables; fixed-width virtualized tables retain their existing sizing. Margins and viewport padding are still excluded separately.

AI assistance: Codex (GPT-6), following AGENTS.md and the testing, tooling and PR guidance.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change.
  • Filled out test instructions.
  • Updated documentation where applicable (the Storybook reproduction; no public component API change).
  • Looked at the Accessibility Practices for this feature - Aria Practices.
  • I understand every change in this PR and can explain why it's there.
  • If AI-assisted, I followed our AI contribution guidance and pointed my assistant at AGENTS.md.

📝 Test Instructions:

Run yarn test --runInBand --shard=N/4 for each N from 1 to 4, yarn test:ssr, and yarn lint. Run yarn vitest run --config=vitest.browser.config.ts --project=<browser>-desktop --maxWorkers=1 --no-file-parallelism for Chromium, Firefox and WebKit in turn.

Latest follow-up verification: all four Jest shards passed (384 suites, 8,438 tests passed, 16 skipped); SSR passed (60 suites, 74 tests). Type checking, oxlint, formatting, package lint and Yarn constraints all passed, run serially to limit resource use. The old borderSpacing: 0 test overrides have been removed.

The 21 table browser tests pass in each browser (63 checks). They cover default content-box and explicit border-box columns, controlled keyboard resizing in LTR/RTL, min/max constraints, fixed/percentage widths, viewport and column-collection changes, spanning body cells, intrinsic sizing with populated/empty bodies, borders, fractional spacing, margins, padding and a scaled ancestor, plus virtualized div tables with block, table and inline-table display.

Full browser suites passed: Chromium 176 passed/7 skipped; Firefox 153 passed/30 skipped; WebKit 157 passed/26 skipped. These are the suites' existing skips; no tests were skipped for this change. The previously reported WebKit DropZone failure did not reproduce in this run.

The column-width layout effect is retained deliberately. Column count cannot represent fixed/percentage/min/max widths or content-box padding. An observer-only alternative missed consecutive collection changes in Firefox when the table returned to its previous observed size within a frame; the existing 2→3→4→2-column regression covers this. The corrective measurement runs before paint, with an additional render only when the available column budget changes.

Open React Aria Components → Table → Resizable Flex Table. The table should remain within the 600px flex layout without a min-width: 0 workaround on its ancestors. Existing column padding and box-sizing should remain intact during resizing.

This follow-up did not repeat the earlier manual mouse verification. Touch, screen-reader speech and high-contrast visual regression were not manually verified. No Chromatic jobs were triggered.

🧢 Your Project:

Independent open-source contribution.

@minwookshin

Copy link
Copy Markdown
Contributor Author

Scoped the keyboard test’s input to its document; the full Linux Firefox suite passes locally. The PR notes the separate WebKit failures and unmodified-base check.

@minwookshin
minwookshin marked this pull request as ready for review October 4, 2026 02:11

@snowystinger snowystinger left a comment •

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 you explain what the root issue was? and why this addresses it?

Padding and spacing outside the allocated column widths currently feed back into the next container measurement

Specifically, what does this mean? Where do they do this and why?

@minwookshin

Copy link
Copy Markdown
Contributor Author

The columns used the full container width without accounting for table spacing and borders, which could keep increasing the measured width in a flex layout. The patch subtracts that extra space and uses border-box sizing for the columns.

@snowystinger snowystinger left a comment

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 we're going to need a different approach given the box-sizing, what other options did you think about? I am not excited about how many specific things need to be measured here. I'll continue to mull it over as well.

let style = renderProps.style;
if (layoutState) {
style = {...style, width: layoutState.getColumnWidth(column.key)};
style = {...style, boxSizing: 'border-box', width: layoutState.getColumnWidth(column.key)};

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 would be a breaking change, it will change how peoples tables are currently laid out

let onColumnsChange = tableContainerContext?.onColumnsChange;
let columnCount = filteredState.collection.columns.length;
useLayoutEffect(() => {
onColumnsChange?.(columnCount);

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 feels unrelated to the resizing? When does the table filtering change the number of visible columns?

@minwookshin

Copy link
Copy Markdown
Contributor Author

Agreed, forcing border-box changes existing layouts; the column-count effect was only needed to recalculate border spacing. A container-level fix could avoid that bookkeeping, but size containment also affects intrinsic sizing, so I don’t have a compatible replacement to push yet.

@minwookshin

Copy link
Copy Markdown
Contributor Author

Reworked this to measure the table's extra width without changing column box-sizing or counting columns. Unit/SSR and all three browsers' table regressions pass; I documented the existing WebKit DropZone failure.

@snowystinger snowystinger left a comment

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.

Thanks for trying out something new

Comment on lines +30 to +33
let onResize = () => onResizeEvent();
window.addEventListener('resize', onResize, false);
return () => {
window.removeEventListener('resize', onResizeEvent, false);
window.removeEventListener('resize', onResize, 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.

why?

ref: RefObject<T | undefined | null> | undefined;
box?: ResizeObserverBoxOptions;
onResize: () => void;
onResize: (entry?: ResizeObserverEntry) => void;

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 if we're going to add this, we should add all the entries, just like resize observer. though I'd really prefer not to do it

onResize={onResize}
onResizeEnd={onResizeEnd}>
<Table aria-label="Files" {...otherProps}>
<Table aria-label="Files" style={{borderSpacing: 0}} {...otherProps}>

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.

why? is this necessary? if so, that would indicate there's still a breaking change

let updateWidth = useCallback((entry?: ResizeObserverEntry) => {
let width = scrollRef.current?.clientWidth ?? 0;
let table = tableRef.current;
if (table?.tagName === 'TABLE') {

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 if something is a DIV with display: table? or inline-table?

: 0;
onColumnWidthsChange?.(allocatedWidth);
}, [onColumnWidthsChange, columnWidths]);

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.

don't love this, it's going to be a render late and introduce a new render
I'm assuming this was to solve whatever the issue was with counting columns? can you explain more about the problem here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The sum handles fixed/percentage widths and different box sizing; column count isn't enough. I kept the corrective render before paint because Firefox misses consecutive collection changes through ResizeObserver alone—the 2→3→4→2-column test catches this.

@minwookshin

Copy link
Copy Markdown
Contributor Author

Kept the observer local, switched to computed display, and removed both border-spacing overrides. Unit/SSR and all three browser suites pass; details are in the description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ResizableTableContainer: Column resize to infinite width when wrapped with display: flex

2 participants