Skip to content

[bug] Select popup renders behind consumer sticky headers due to missing overlay z-index #759

Description

@Rydann

What happens

A Select near the top of the page, with the currently selected option deep in the list, opens its popup upward (align-with-selected behavior) and the popup disappears behind our sticky header. It looks clipped, but it is not a viewport or collision problem: hide the sticky header and the popup renders fine (with its internal scroll). The popup simply paints below the header.

Our header is position: sticky; z-index: 100. The Select sits in normal page flow below it and the popup portals to document.body as expected.

Root cause (verified against the published package)

In @cloudflare/kumo 2.12.0, the Select popup and positioner carry no z-index at all:

  • The only z-50 in the select chunk is focus-visible:z-50 on list items:
    grep -o "z-50" dist/chunks/select-*.js matches only inside the item className.
  • Base UI's portaled Select.Positioner defaults to z-index: auto.

So the portaled floating components do not establish an overlay stacking layer of their own, and application UI with a positive z-index (a normal sticky header, for example) can paint above the open popup even though it is portaled to document.body. The magnitude does not matter, any positive value on the header reproduces it.

This is especially visible with Select's align-with-selected behavior, because the popup extends upward across application chrome when the selection sits deep in the list. The issue appears to be shared by the other floating components built on the same pattern (the popover, dropdown, tooltip and combobox chunks reference no z-index utilities either), but Select is the only one we reproduced and verified.

Minimal repro

import { useState } from "react";
import { createRoot } from "react-dom/client";
import { Select } from "@cloudflare/kumo/components/select";
import "@cloudflare/kumo/styles/standalone";

const ITEMS = Array.from({ length: 15 }, (_, i) => ({ label: `Option ${i + 1}`, value: `opt${i + 1}` }));

function App() {
  const [v, setV] = useState("opt15");
  return (
    <>
      <div style={{ position: "sticky", top: 0, zIndex: 100, padding: 12, background: "#141414" }}>sticky header</div>
      <div style={{ padding: 24 }}>
        <Select label="15 options, last one selected" items={ITEMS} value={v} onValueChange={(x) => setV(x ?? "opt15")} />
      </div>
    </>
  );
}

createRoot(document.getElementById("root")!).render(<App />);

Open the Select: the options before the selection extend upward and vanish behind the header. Remove the header's zIndex and the popup renders fine.

Image

Workaround we are using

/* open Base UI positioners get a layer above app chrome */
[data-side][data-align][data-open] { z-index: 101; }

This targets Base UI internal data attributes and the value is just whatever clears our header, so we are not proposing it as the fix.

Ask

Define how floating content stacks above consumer page chrome. That could be a z-index on the positioners, a Kumo-defined overlay layer or token, an isolated portal root, or exposing the positioner props on Select (like Combobox.Content got in 2.10.0, 0c58325) so consumers can manage stacking through a supported API instead of the internal attributes above.

Setup

@cloudflare/kumo 2.12.0 (standalone stylesheet, granular imports), @base-ui/react 1.7.0, React 19.2.8, Vite 8.2.2, Chrome on Windows 11.

Activity

  1. mattrothenberg commented on Sep 8, 2026

    @mattrothenberg
    Collaborator

    Thanks for the detailed report! We gave this a quick try with a minimal Kumo page: sticky header at z-index: 100, Select near the top, Option 15 selected, and selected-item alignment enabled.

    We’re seeing the portaled popup render above the header in our setup, both with and without root isolation, so we haven’t been able to reproduce it just yet.

    Would you be able to share a CodeSandbox or small repo with the exact app setup and package versions? That’d help us narrow down what’s different.

    One thing that may be worth trying in the meantime: Base UI recommends adding isolation: isolate to the app root. It often helps with page-level stacking-context conflicts around portaled content:

    .root {
      isolation: isolate;
    }
  2. Rydann commented on Sep 12, 2026

    @Rydann
    Author

    Here's a small repo with our exact setup and pinned versions: https://github.com/Rydann/kumo-select-zindex-repro

    pnpm install && pnpm dev then open the Select (Option 15 pre-selected) and the popup's upper rows paint behind the sticky header.

    isolation: isolate does sidestep it, but that puts the burden on every consumer.

  3. mattrothenberg commented on Sep 12, 2026

    @mattrothenberg
    Collaborator

    isolation: isolate does sidestep it, but that puts the burden on every consumer.

    Because Kumo is powered by Base UI under the hood, and because the Base UI docs call this out as a pre-requisite for proper portaling, this is an OK compromise to us.

    https://base-ui.com/react/overview/quick-start#portals

    I'll make sure to update our docs to call this out more explicitly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions