Skip to main content
Urbicon UI

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

sticky
stickyOffset: 48px

Scroll the page to see the pinned layer(s). Adjust stickyOffset to simulate a fixed app-shell top bar.

Grouping: Department
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

No pinning is the default. The bare prop is the same as both: toolbar, header and group header all pin.
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

Pin only the toolbar; the column header scrolls with the rest of the table.
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

Pin only the column header (plus group header when grouping is active). The toolbar scrolls with the page.
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

If your layout has a fixed top bar, pass its height in pixels so the pin lands just below it.
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

Replace SmartFilterBar entirely while keeping the sticky behavior.
<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

The table fills the available viewport height. Header pinned, toolbar + pagination fixed, rows scroll in both directions. Supersedes the sticky prop; desktop-only (mobile keeps document scroll).
<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 unintentional overflow wrapper.
  • 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 to fit="viewport" to contain it instead.
  • unstyled mode strips the sticky classes, because pinning is a layout function rather than pure styling. Apply your own via slotClasses.toolbar / slotClasses.thead when 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 past 100dvh and you get a second scrollbar next to the table's own. The container reports its resolved mode as data-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 } }.