Sticky Pinning
Pin the toolbar, column header, and group header to the top of the scroll ancestor on long lists. Keeps context visible while scrolling through hundreds of rows.
Overview
The sticky prop pins one or more of the table's three
contextual layers — toolbar (L1), column header (L2), and group header (L3 when grouping is active). The pin position is offset by the
height of each preceding layer using CSS custom properties, so the layers stack naturally below
your app shell's top bar.
For tables wider than the viewport, reach for fit="viewport" (see below) — it contains both axes of scroll inside the table, so the page never scrolls sideways.
Playground
Scroll the page to see the pinned layer(s). Adjust stickyOffset to simulate a fixed app-shell top bar.
Name | Role | Department | Location | |
|---|---|---|---|---|
Platform (16 items) | ||||
Emma Wilson | Staff Engineer | Platform | Berlin | |
Sofia Martinez | Engineering Manager | Platform | Munich | |
Noah Kim | DevOps Engineer | Platform | Hamburg | |
Ethan Müller | QA Engineer | Platform | Hamburg | |
Emma Wilson | Staff Engineer | Platform | Berlin | |
Sofia Martinez | Engineering Manager | Platform | Munich | |
Noah Kim | DevOps Engineer | Platform | Hamburg | |
Ethan Müller | QA Engineer | Platform | Hamburg | |
Emma Wilson | Staff Engineer | Platform | Berlin | |
Sofia Martinez | Engineering Manager | Platform | Munich | |
Noah Kim | DevOps Engineer | Platform | Hamburg | |
Ethan Müller | QA Engineer | Platform | Hamburg | |
Emma Wilson | Staff Engineer | Platform | Berlin | |
Sofia Martinez | Engineering Manager | Platform | Munich | |
Noah Kim | DevOps Engineer | Platform | Hamburg | |
Ethan Müller | QA Engineer | Platform | Hamburg | |
Design (12 items) | ||||
Liam Chen | Product Designer | Design | Hamburg | |
Olivia Brown | UX Researcher | Design | Munich | |
Isabella Singh | Design Lead | Design | Berlin | |
Liam Chen | Product Designer | Design | Hamburg | |
Olivia Brown | UX Researcher | Design | Munich | |
Isabella Singh | Design Lead | Design | Berlin | |
Liam Chen | Product Designer | Design | Hamburg | |
Olivia Brown | UX Researcher | Design | Munich | |
Isabella Singh | Design Lead | Design | Berlin | |
Liam Chen | Product Designer | Design | Hamburg | |
Olivia Brown | UX Researcher | Design | Munich | |
Isabella Singh | Design Lead | Design | Berlin | |
Product (12 items) | ||||
James Park | Frontend Developer | Product | Remote | |
Lucas Weber | Backend Developer | Product | Berlin | |
Mia Zhang | Product Manager | Product | Remote | |
James Park | Frontend Developer | Product | Remote | |
Lucas Weber | Backend Developer | Product | Berlin | |
Mia Zhang | Product Manager | Product | Remote | |
James Park | Frontend Developer | Product | Remote | |
Lucas Weber | Backend Developer | Product | Berlin | |
Mia Zhang | Product Manager | Product | Remote | |
James Park | Frontend Developer | Product | Remote | |
Lucas Weber | Backend Developer | Product | Berlin | |
Mia Zhang | Product Manager | Product | Remote | |
Data (8 items) | ||||
Aisha Patel | Data Scientist | Data | Berlin | |
Alexander Novak | ML Engineer | Data | Munich | |
Aisha Patel | Data Scientist | Data | Berlin | |
Alexander Novak | ML Engineer | Data | Munich | |
Aisha Patel | Data Scientist | Data | Berlin | |
Alexander Novak | ML Engineer | Data | Munich | |
Aisha Patel | Data Scientist | Data | Berlin | |
Alexander Novak | ML Engineer | Data | Munich | |
Platform (16 items)
Design (12 items)
Product (12 items)
Data (8 items)
Modes
sticky on its own
Name | Role | Department | Location |
|---|---|---|---|
Emma Wilson | Staff Engineer | Platform | Berlin |
Liam Chen | Product Designer | Design | Hamburg |
Sofia Martinez | Engineering Manager | Platform | Munich |
James Park | Frontend Developer | Product | Remote |
Aisha Patel | Data Scientist | Data | Berlin |
Noah Kim | DevOps Engineer | Platform | Hamburg |
<Table
{items}
{columns}
sticky
/>Toolbar only
Name | Role | Department | Location |
|---|---|---|---|
Emma Wilson | Staff Engineer | Platform | Berlin |
Liam Chen | Product Designer | Design | Hamburg |
Sofia Martinez | Engineering Manager | Platform | Munich |
James Park | Frontend Developer | Product | Remote |
Aisha Patel | Data Scientist | Data | Berlin |
Noah Kim | DevOps Engineer | Platform | Hamburg |
<Table
{items}
{columns}
sticky="toolbar"
/>Header only
Name | Role | Department | Location |
|---|---|---|---|
Emma Wilson | Staff Engineer | Platform | Berlin |
Liam Chen | Product Designer | Design | Hamburg |
Sofia Martinez | Engineering Manager | Platform | Munich |
James Park | Frontend Developer | Product | Remote |
Aisha Patel | Data Scientist | Data | Berlin |
Noah Kim | DevOps Engineer | Platform | Hamburg |
<Table
{items}
{columns}
sticky="header"
/>With stickyOffset for app-shell top bar
Name | Role | Department | Location |
|---|---|---|---|
Emma Wilson | Staff Engineer | Platform | Berlin |
Liam Chen | Product Designer | Design | Hamburg |
Sofia Martinez | Engineering Manager | Platform | Munich |
James Park | Frontend Developer | Product | Remote |
Aisha Patel | Data Scientist | Data | Berlin |
Noah Kim | DevOps Engineer | Platform | Hamburg |
<Table
{items}
{columns}
sticky
stickyOffset={64}
/>Custom toolbar
The default toolbar is the SmartFilterBar. Override
it with the toolbar snippet — the custom content inherits
the same sticky wrapper.
Custom toolbar snippet
<Table {items} {columns} sticky enableSmartFilter={false}>
{#snippet toolbar()}
<div class="flex items-center justify-between p-3">
<h3 class="text-base font-semibold">Active employees</h3>
<button class="...">Export CSV</button>
</div>
{/snippet}
</Table>Contained scroll — fit="viewport"
Page-relative sticky pinning and in-table horizontal
scroll are mutually exclusive: a single element cannot be both a sticky-pin host and a
scroll ancestor. So with sticky="header", a table
wider than the viewport pushes its horizontal overflow onto the page — the whole layout
scrolls sideways.
fit="viewport" resolves this by making the table its
own scroll container. It is height-capped to the viewport (measured automatically — no magic max-height), the column and group headers pin to the
top of the box, and the toolbar + pagination stay fixed outside the scrolling area.
Only the rows scroll, horizontally and vertically.
Full-height list page
<Table
{items}
{columns}
fit="viewport"
/>fit="viewport" supersedes sticky and stickyOffset (the measured top absorbs app-shell
offsets). It is mutually exclusive with virtualized, which keeps its own virtualHeight scroll box.
Caveats
- Sticky pinning anchors to the nearest scrollable ancestor. If you wrap the table in a
container with
overflow: auto/hidden, the pin will bind to that container — usually what you want inside a Drawer body, less so inside an unintentionaloverflowwrapper. - Enabling
sticky="header"or"both"disables the table's internal horizontal scrolling (the scroll area can't be both a sticky pin host AND a scroll ancestor at the same time). Very wide tables fall back to page-level horizontal scrolling — switch tofit="viewport"to contain it instead. unstyledmode strips the sticky classes, because pinning is a layout function rather than pure styling. Apply your own viaslotClasses.toolbar/slotClasses.theadwhen running unstyled.- A
fit="viewport"box reaches the bottom of the viewport, so it assumes nothing sits below it. Bottom padding on an ancestor, or a following sibling, pushes the page past100dvhand you get a second scrollbar next to the table's own. The container reports its resolved mode asdata-fit, so a layout can drop that inset for the desktop widths where the cap applies:@media (min-width: 48rem) { main:has([data-fit='viewport']) { padding-block-end: 0 } }.