feat(DataGrid, Pagination): sticky footer with numbered pagination and rows per page - #1920
Merged
Stephen Watkins (stephenjwatkins) merged 14 commits intoSep 2, 2026
Merged
Conversation
Adds `<Pagination.Pages />` alongside the existing `<Pagination.Dropdown />` for cases where the user should be able to see and reach nearby pages directly. The scheme is inferred from the child rather than configured, since the two schemes differ only in how they let the user reach a page. Also adds optional `First`/`Last` jump buttons, which the numbered scheme needs to keep the ends of a long range reachable once they truncate.
Adds a `renderFooter` prop that renders a sticky bar pinned to the bottom of the data grid's scroll container, modeled on the existing sticky header: it reuses the edge interceptors for its shadow-on-scroll and pins horizontally so it stays put while the rows scroll sideways. `<DataGrid.Footer />` lays the bar out with purely positional `start`, `center`, and `end` slots, so it stays unaware of what it holds and the data grid isn't tied to any one pagination scheme. `<DataGrid.Pagination />` and `<DataGrid.RowsPerPageMenu />` are fully controlled presets for the common case; either can be swapped for a custom control, or `renderFooter` can skip `<DataGrid.Footer />` entirely.
The shadow is absolutely positioned, so its containing block is the shell's padding box, which the 1px top border pushes down. That shifted the whole 8px band down onto the border and painted over it, so while scrolled the crisp rule vanished and the ramp terminated in nothing but the bar -- reading as a thicker, mushier shadow than the header's, which has no border to clear. Pulling the band up by the border width lands it on the border-box edge instead. Measured against the header, both are now 8-row ramps over the same values, each terminated by a hard edge.
Drops `sticky-shadow-opacity` from 0.5 to 0.35, which softens the header's and footer's bands from a darkest value of rgb(188,199,219) to rgb(208,215,229). The token is shared, so this lightens the frozen column shadows by the same amount rather than leaving them heavier than the horizontal ones.
Design has two Page Toolbars: a 38px "Default" and the 24px "Small" the data grid footer embeds. Only the small one was built, so `size` now picks between them, defaulting to `md` to match design's naming. The sizes are described as component tokens on `.sizeSm`/`.sizeMd` so the squares, jump widths, and both gaps move together rather than each part branching on the size. The chevron is the exception: `<Icon />` is sized in React, so the nav button still branches, the way `<Button />` does. Two things the design context turned up about the small variant along the way: the page numbers are `small-button` type rather than `subtitle2` (same 12px medium, but the semantic token), and the ellipsis is Blue 800 like the page numbers, not Gray 500. It also shouldn't take a pointer cursor, so `cursor: pointer` moves off the shared box class onto the three classes that are actually buttons. At page 5 of 10 with first/last buttons, both sizes come out to design's widths exactly: 594px for `md`, 396px for `sm`.
The larger size came straight from Figma, and four of its numbers weren't on the space scale. Rather than leave literals in the size tokens, each one moves to its nearest sensible step: control size 38px -> space.5 (40px) jump width 72px -> space.9 (72px, exact) gap between 18px -> space.2 (16px) control size 24px -> space.3 (24px, exact) [small] jump width 54px -> space.7 (56px) [small] `space.*` for a control dimension follows what's already here, e.g. `<NexusLayoutActions />`, `<Menu />`, and the `<ColorPicker />` swatches. The page-row gap was the one value with no defensible neighbor: 6px sits exactly between space.0.5 and space.1, and rounding it up to 8px would loosen a run of numbers that reads as a single group. Both sizes now use space.0.5 there, which turns the gap into a constant, so it drops out of the size tokens entirely and sits on `.pageRow`. The cost is that neither size is pixel-identical to Figma anymore: `md` lands at 592px against design's 594, and `sm` at 400px against 396. The squares, chevrons, radius, and type are all unchanged.
Design pairs the rows per page control with the numbered pagination scheme and gives it the same size axis, and the control never depended on the data grid, so it belongs alongside the scheme it's drawn to match. It isn't a `<Pagination />` child: that slot takes a scheme, and the two are siblings in design. `<DataGrid.RowsPerPage />` stays as a thin preset that pins the size the footer bar is drawn to, mirroring `<DataGrid.Pagination />`. The size values resolve to the nearest tokens on the space scale, which lands the medium control within a quarter pixel of design's 209 and the small within one and a third of its 178. The chevron holds a 24px box at both sizes, which is what design's layout arithmetic closes on.
Stephen Watkins (stephenjwatkins)
force-pushed
the
feat/datagrid-sticky-footer
branch
from
September 2, 2026 17:04
87a6bd3 to
9ddf5df
Compare
Design specifies gray-800 for both the label and the count, which maps to `color.neutral.800`. The control was on `neutral.900`, pure black, so the text rendered a shade darker than drawn.
…token The gradient band the grid uses for its sticky edges reads heavier under the footer than under the header: it stacks a hard-edged ramp on top of a white backdrop that wipes the row it covers, and the footer's border sits right beneath it, so the two compound into a thick smear rather than a shadow. `shadow.stuck-from-bottom` is the token for exactly this -- an element stuck to the bottom of a scroll port -- and `<Modal />`'s sticky footer already uses it. Being a real blur, it decays to transparent over the rows instead of fading to white, at roughly half the weight. It also lands on the border box, so the band no longer needs pulling up past the border to avoid painting over it. The header keeps the gradient band: its shadow is cast by each sticky cell, and adjacent box shadows would seam at every column boundary. The footer is a single full-width element, so it has no such constraint.
The header and frozen columns can't take `shadow.stuck-from-*` the way the footer does: their shadows are cast by table cells, and box shadows don't paint on those once the table's borders are collapsed. Their gradient band mimics the token instead -- `color.neutral.300` at a quarter, which is what the token resolves to -- so every sticky edge of the grid reads at the same weight. Also drops the white backdrop the header's band laid under its ramp. It was wiping the top of whichever row sat beneath the header, which read as the row being cut short rather than passing under it. Without it the band decays to transparent over the row, as the frozen columns' bands already did.
A shadow this light is imperceptible under the dark header: the navy-to-row step is a hard, high-contrast edge, and it swamps a tint of twenty-odd values. The header now carries its own opacity, at a weight that reads against the default and emphasized backgrounds while staying well short of the band this branch started from. `.headerSecondary` is light enough that the edge does not compete, so it drops back to the weight the frozen columns and the footer are drawn at.
Read against the navy, the shadow holds up better at a weight closer to what the grid shipped with than to the rest of its sticky edges.
Design has two variants of the numbered scheme that neither the jump buttons nor the chevrons cover: a bare run of pages, and a run with the chevrons but no jump buttons. The chevrons now come and go with their handlers the way the jump buttons already do, so both fall out of the same API.
The blue text shift these picked up from the dropdown scheme's buttons lands harder here, since these rest at primary.800 rather than primary.700. They tint their box instead, which is what the page buttons beside them already do.
Stephen Watkins (stephenjwatkins)
marked this pull request as ready for review
September 2, 2026 18:00
Stephen Watkins (stephenjwatkins)
requested review from
a team
as code owners
September 2, 2026 18:00
Stephen Watkins (stephenjwatkins)
deleted the
feat/datagrid-sticky-footer
branch
September 2, 2026 19:59
11 tasks
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.
📝 Changes
Adds a sticky footer to
<DataGrid />and the pagination pieces it's built from.<DataGrid />renderFooterrenders a bar pinned to the bottom of the scroll container, modeled on the existing sticky header — it reuses the edge interceptors for its shadow-on-scroll and pins horizontally so it stays put while rows scroll sideways.<DataGrid.Footer />lays the bar out with purely positionalstart,center, andendslots, so it stays unaware of what it holds and the data grid isn't tied to any one pagination scheme.<DataGrid.Pagination />and<DataGrid.RowsPerPage />are fully controlled presets for the common case. Either can be swapped for a custom control, orrenderFootercan skip<DataGrid.Footer />entirely.0.5→0.35), which affects the frozen column shadows as well as the new footer's.<Pagination /><Pagination.Pages />is a numbered scheme alongside the existing<Pagination.Dropdown />, for when the user should be able to see and reach nearby pages directly. The scheme is inferred from the child rather than configured. OptionalFirst/Lastjump buttons keep the ends of a long range reachable once it truncates.<Pagination.RowsPerPage />is a fully controlled page-size menu. It's a sibling of<Pagination />rather than a child, since that slot takes a scheme and the two sit side by side in design.sizeofsmormd, resolved to the nearest tokens on the space scale.✅ Checklist
Easy UI has certain UX standards that must be met. In general, non-trivial changes should meet the following criteria:
Strikethroughany items that are not applicable to this pull request.