Skip to content

feat(table): stack rows into blocks below a container width - #80

Merged
Viserion77 merged 1 commit into
mainfrom
feat/table-stacked-mode
Oct 6, 2026
Merged

Viserion77 merged 1 commit into
mainfrom
feat/table-stacked-mode

Conversation

@Viserion77

Copy link
Copy Markdown
Owner

Summary

TTable gains stackBelow: under a container width the grid becomes one block per row, so nothing disappears behind a horizontal scroll on a phone.

Until now the only answer on a narrow screen was the wrapper's horizontal scroll, with no affordance saying there was more to the right. Measured on a three-column ledger at 390px: the wrapper overflowed by 68px and the rightmost column — half the information on screen — was simply cut off, while the middle column was squeezed to ~110px and its text broke over four or five lines, so row heights ran from 60 to 133px.

<TTable
  :columns="[
    { key: 'entry',  label: 'Lançamento',       stack: 'title' },
    { key: 'real',   label: 'Saldo real' },
    { key: 'credit', label: 'Saldo do crédito' },
  ]"
  :rows="rows"
  stack-below="sm"
/>

Columns take stack?: 'title' | 'field' | 'hidden', defaulting to field. At most one title — the first column claiming it wins, since two headings in one block is not a block and honouring the last would make the result depend on column order in a way nobody would guess.

Measured on the container, not the viewport

A table can sit in a narrow panel on a wide screen, and that is the case a media query cannot see. In one 1440px page a 372px wrapper stacks while a 742px wrapper beside it stays a table, both with zero overflow.

stackBelow takes a breakpoint name rather than a free length, which is narrower than it looks: a container query cannot read a custom property in its condition, so an arbitrary value would need a ResizeObserver per table — and this component is used two dozen times across the apps that asked for this. The pixel literals in the stylesheet are held equal to treeTokens.breakpoint by the same test that already guards TShow/THide, so they cannot drift.

The accessibility half was the real work

display: block drops the implicit table semantics in several screen readers: rows stop being rows and cells stop being cells. role is now declared on the table, both rowgroups, every row, every column header and every cell. Each is the element's own implicit role, so nothing changes above the breakpoint — they exist to survive the switch. Verified in the accessibility tree, stacked:

- table "Lançamentos":
  - rowgroup:
    - row "Abrir Mercado — compra do mês Saldo real R$ 1.284,30 …":
      - cell "Abrir Mercado — compra do mês"
      - cell "Saldo real R$ 1.284,30"
      - cell "Saldo do crédito R$ 412,00"

Every value carries its column, which is what replaces the hidden header row.

A row link has to cover the whole block

rowState, rowHref/rowTo and rowActivatable keep working in a block — and the stretched link covers the block, not just its heading. That took two attempts. Outside stacked mode every cell after the first is raised above the link overlay so a button in the actions column stays clickable; stacked, those same cells are plain text under the title, and raising them punched a hole in the link. Inside the container query only cells that actually contain something focusable are raised — and never the first, which holds the overlay: positioning it makes it the overlay's containing block, and inset: 0 then covers that cell alone. That is the exact trap the existing comment in the stylesheet warns about, and the first fix walked into it.

Validation

  • pnpm lint
  • pnpm typecheck
  • pnpm test — 1174 passing, 13 new
  • pnpm build — plus build:site, codegen:native:check, typecheck:strict-templates, check:theme
  • pnpm test:e2e — 11/11

Measured in a browser, against the four conditions this change has to meet:

result
390px viewport, and a 390px container inside a 1440px one wrapper overflow 0, stacked, every value labelled; a 742px container on the same page stays a table
A table without stackBelow container-type: normal, display: table, header visible, overflow 0 — unchanged
Accessibility tree table → rowgroup → row → cell, each cell carrying its column
rowState + rowHref in a block state applied, links on every row with the rowLabel name, elementFromPoint at the block centre finds the link

Notes

  • Accessibility: the explicit roles are the substance here, not decoration. Without them the feature would trade a horizontal scroll for a screen reader announcing a run of unrelated text.
  • Two existing tests changed. is a reading table, not an unmanaged grid and activates through a real button, keeping the row a row asserted role === undefined. Their comments say what they were actually defending — not role="grid" on the table, not role="button" on the row — so they now assert the correct role instead of the absence of one. Same intent, stated more precisely.
  • Containment is scoped. container-type: inline-size applies contain: layout inline-size style, so it is set only on a wrapper carrying a stack-below modifier rather than on every table in the library.
  • Follow-up: stackSpan — two fields sharing a line inside the block — is not here. It was raised as a maybe and arrived without a measurement; worth doing if a block with many fields turns out too tall.

- Added `stackBelow` prop to TTable for controlling stacking behavior below specified breakpoints.
- Implemented logic to render table rows as blocks on narrow containers, enhancing accessibility and usability.
- Updated styles to support stacked layout, ensuring proper display and semantics in both stacked and non-stacked modes.
- Enhanced documentation for TTable to include details about new stacking behavior and its implications.
- Added tests to verify the correct rendering and behavior of the stacked mode.
@Viserion77 Viserion77 self-assigned this Oct 6, 2026
@Viserion77
Viserion77 merged commit d5add8b into main Oct 6, 2026
3 checks passed
@Viserion77
Viserion77 deleted the feat/table-stacked-mode branch October 6, 2026 23:41
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.

1 participant