SharePointVault ~/gotchas/spfx ⌕ Search 404 pages, error messages, scripts…Ctrl K ★ Star
M↓ README.mdM↓ fixed-dropdowns-in-transformed-panels.md
sharepoint-vault›gotchas›spfx›fixed-dropdowns-in-transformed-panels.mdhistoryedit on GitHub
● GOTCHAspfx· 2 min read

position: fixed dropdowns go off-screen inside animated panels — the CSS transform trap

applies to: Any web UI (bites hard in SPFx overlays)reviewed: 2026-07-15#spfx#css#react#ux

A transform on any ancestor (even a settled translateX(0)) becomes the containing block for position: fixed, so a dropdown in a slide-in panel lands off-screen — render floating elements through a portal to document.body.

transform na jakémkoli předku (i doběhlý translateX(0)) se stane containing blockem pro position: fixed, takže dropdown v zasouvacím panelu skončí mimo obrazovku – plovoucí prvky renderuj přes portál do document.body.

Symptom

An autocomplete/people-picker dropdown works perfectly in a normal page — then you mount the same component inside a slide-in side panel and the dropdown vanishes. The search works (network tab full of results, state updates firing), but nothing visible appears. Sometimes it renders in a bizarre corner instead.

Cause

Pure CSS, no SharePoint involved: an ancestor with transform (also filter, perspective, or certain will-change values) becomes the containing block even for position: fixed descendants. Slide-in panels animate with transform: translateX(…) — so your "fixed to the viewport" dropdown is silently fixed to the panel instead, and coordinates computed from getBoundingClientRect() (viewport-relative) put it far outside the panel's box — typically off-screen.

The cruelty: translateX(0) after the animation finishes still counts. The panel looks static; the containing block trap remains.

Fix

Render the dropdown through a portal to document.body — out of the transformed subtree — and position it from the input's viewport rect:

const rect = inputRef.current.getBoundingClientRect();
// state: { top: rect.bottom + 2, left: rect.left, width: rect.width }

{open && ReactDOM.createPortal(
  <div style={{ position: 'fixed', top: pos.top, left: pos.left, width: pos.width, zIndex: 2000 }}>
    {results.map(renderItem)}
  </div>,
  document.body
)}

Rule of thumb: any floating element (dropdown, tooltip, menu) inside any animated overlay goes through a portal. Make it the default and you'll never debug this again.

Notes

  • Select items with onMouseDown, not onClick — mousedown fires before the input's onBlur, so the dropdown doesn't close a tick before the click lands.
  • Close-on-outside-click: a document-level mousedown listener (or onBlur with a short timeout) — remember the portal means the dropdown is not a DOM child of your component, so contains() checks must include the portal node.
  • Reposition on scroll/resize if the overlay's content can scroll under the input.
  • Diagnostic signature: "works on the page, invisible in the panel, console shows results" → it's this, every time.
Was this helpful?ask or add your experience on GitHub →
Saved you an hour? A star on GitHub helps other SharePoint people find the vault.★ Star on GitHub
last reviewed: 2026-07-15last updated: 2026-09-24historyedit on GitHubview source