[DevTools] Fix WhatChanged scrolling out of view in profiler sidebar
Project: facebook/react | Language: JavaScript | Concepts: refactoring, testing-strategy | Difficulty: mid
Link: https://github.com/react/react/pull/36244
The problem
In the React DevTools Profiler sidebar, the "WhatChanged" summary and component badges were rendered inside the same scrollable area as the commit list. When a component had many render timestamps, scrolling down the list would cause the "WhatChanged" context to scroll out of view, making it harder to analyze why a component rendered without extra manual scrolling.
Why it's non-trivial
A naive fix might just move the "WhatChanged" section outside the scrollable area, but this can create layout problems:
- If "WhatChanged" is tall (e.g. many changed props/hooks), it could take up too much vertical space, leaving little room for the commit list.
- The UI needs to remain accessible and usable for both mouse and keyboard users.
- The commit list must remain scrollable independently, while the summary context should always be visible (or easily collapsible).
The approach
The PR restructures the sidebar into two regions: a sticky, optionally collapsible header for the summary/badges, and a scrollable commit list below. Key changes:
1. CSS: Split layout and add sticky header styles
.Content {
- padding: 0.5rem;
+ padding-bottom: 0.5rem;
user-select: none;
- overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ height: 100%;
}
Explanation: The .Content div is now a flex column, taking full height, so children can be split into sticky and scrollable regions.
+.RenderInfo {
+ flex: 0 0 auto;
display: flex;
flex-direction: column;
gap: 0.5rem;
+ padding: 0.5rem;
+ border-bottom: 1px solid var(--color-border);
+ background: var(--color-background);
+}
+
+.RenderInfoContent {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.CommitList {
+ flex: 1;
+ min-height: 0;
+ overflow-y: auto;
+ padding: 0.5rem;
+}
Explanation:
.RenderInfois the sticky header: summary, badges, "WhatChanged"..CommitListis the scrollable area for the commit list.
2. JS: Move summary and commit list into separate sibling containers
- <div className={styles.Content} onKeyDown={handleKeyDown} tabIndex={0}>
- {node != null && (
- <InspectedElementBadges
- hocDisplayNames={node.hocDisplayNames}
- compiledWithForget={node.compiledWithForget}
- />
- )}
- <WhatChanged fiberID={selectedFiberID as any as number} />
- {listItems.length > 0 && (
- <div>
- <label className={styles.Label}>Rendered at: </label>
- {listItems}
- </div>
- )}
- {listItems.length === 0 && (
- <div>Did not render on the client during this profiling session.</div>
- )}
- </div>
+ <div className={styles.Content}>
+ <div className={styles.RenderInfo}>
+ <div className={styles.RenderInfoContent}>
+ {node != null && (
+ <InspectedElementBadges
+ hocDisplayNames={node.hocDisplayNames}
+ compiledWithForget={node.compiledWithForget}
+ />
+ )}
+ <WhatChanged fiberID={selectedFiberID as any as number} />
+ </div>
+ </div>
+ <div className={styles.CommitList} onKeyDown={handleKeyDown}>
+ {listItems.length > 0 && (
+ <div>
+ <label className={styles.Label}>Rendered at: </label>
+ {listItems}
+ </div>
+ )}
+ {listItems.length === 0 && (
+ <div>
+ Did not render on the client during this profiling session.
+ </div>
+ )}
+ </div>
+ </div>
Explanation:
- The summary (
RenderInfo) and commit list (CommitList) are now siblings, not stacked inside the same scroll area. - The summary always stays visible; the commit list scrolls independently.
- Keyboard navigation is preserved by moving
onKeyDownto the commit list.
3. Badges layout updated for wrapping and spacing
-.Root {
- user-select: none;
- display: inline-flex;
-}
-
-.Root *:not(:first-child) {
- margin-left: 0.25rem;
+ flex: 1 1 auto;
+ min-width: 0;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: start;
+ gap: 0.25rem;
}
Explanation:
- Badges now wrap if there are many, preventing overflow and keeping the header compact.
From the review thread
hoxyq: Also, please see failed Flow jobs, you can reproduce it locally with
yarn flow dom-browser.
hoxyq: Thanks for highlighting the issue! Can we just put the WhatChanged and badges in a sticky subheader, same like we do it for the component name?
Without any expand / collapse mechanics, I think that should address the issue?
Kertsu: Yes, that was my original instinct too. Just lift
WhatChangedand the badges out of the scroll container so they sit above the commit list as a static subheader.The reason I added the collapse mechanic is for cases where
WhatChangedbecomes tall, e.g. when a component has many changed hooks or props, the subheader can take up a significant portion of the sidebar height, leaving little room for the commit list. The collapsible lets users trade that context off for more list space when they need it.That said, I understand this adds interaction complexity that may not be in line with the existing DevTools patterns. Happy to drop it and go with the simpler non-collapsible sticky subheader if you think the tradeoff is acceptable.
Just want to make sure we're aligned before I push another revision.
Takeaways
- When separating UI regions, use flex layouts to allow sticky headers and independently scrollable content.
- Always consider the impact of large/variable-height content in sticky headers; provide a way to reclaim space if needed.
- Keep accessibility and keyboard navigation in mind when refactoring scroll and focus management.
- For visual context that is critical to user workflows, ensure it remains visible during navigation, not hidden by scrolling.