Skip what users can't see

Skip layout for off-screen rows; hide toggled UI instead of unmounting.

From vercel-react-best-practices: rendering-content-visibility and rendering-activity.

Lay out 1,000 messages

Press Render both, then scroll the right list.

Plain listBad
Layout
–
First frame
–
Laid out
–
.message-item { }
content-visibilityGood
Layout
–
First frame
–
Laid out
–
content-visibility: auto; contain-intrinsic-size: 0 80px
.message-item {
  content-visibility: auto;
  contain-intrinsic-size: 0 80px;
}

function MessageList({ messages }: { messages: Message[] }) {
  return (
    <div className="overflow-y-auto h-screen">
      {messages.map(msg => (
        <div key={msg.id} className="message-item">
          <Avatar user={msg.author} />
          <div>{msg.content}</div>
        </div>
      ))}
    </div>
  )
}

No size hint, jumpy scrollbar

Scroll both lists and watch the total height.

No intrinsic sizeBad
Total height
–
Height changes
0
content-visibility: auto
With a size estimateGood
Total height
–
Height changes
0
contain-intrinsic-size: 0 80px
.message-item {
  content-visibility: auto;
  contain-intrinsic-size: auto 80px;
}

An expensive menu

Open, filter and tick, then close and reopen.

Conditional renderBad

Not opened yet

{isOpen && <ExpensiveMenu />}
ActivityGood

Not opened yet

<Activity mode={isOpen ? 'visible' : 'hidden'}>
import { Activity } from 'react'

function Dropdown({ isOpen }: Props) {
  return (
    <Activity mode={isOpen ? 'visible' : 'hidden'}>
      <ExpensiveMenu />
    </Activity>
  )
}

Why it matters, pros and cons

Why it matters

content-visibility skips off-screen layout; Activity hides UI without unmounting.

Pros

  • Two CSS declarations, no virtualization library
  • Find-in-page and anchors still reach rows
  • Reopened UI keeps its state

Cons

  • React still renders every row; virtualize at tens of thousands
  • A poor size estimate makes the scrollbar jump
  • Hidden trees hold memory; reset forms should unmount