Skip to content

Positioned elements paint in tree order; no CSS stacking order or zIndex #481

Description

@Ernxst

Problem

Found in the consuming app on 0.16.0-fork.1. Positioned elements paint in tree order, and zIndex is not supported, so a positioned element can't be placed early in the tree without the content after it painting over it. Browsers apply CSS stacking order instead: positioned descendants paint above in-flow, non-positioned content regardless of source order, and z-index orders positioned elements among themselves.

In the app this blocks an accessibility fix. The dock is an absolutely positioned layer at the bottom of the window. To make it the first thing Tab reaches, it has to come before <main> in the tree. On the web that changes nothing visually. In GPU-IX the opaque <main> then paints over the dock and it disappears.

Minimal repro, a 200×100 root with position: relative, captured with renderer.captureScreenshot():

const overlay = <div style={{ position: "absolute", left: 20, top: 20, width: 100, height: 50, background: "#ff0000" }} />;
const page = <div style={{ width: 200, height: 100, background: "#0000ff" }} />;
// overlay first: [overlay, page]   overlay last: [page, overlay]
Tree order GPU-IX Chromium
overlay, then page all blue: overlay hidden red overlay over blue
page, then overlay red overlay over blue red overlay over blue

zIndex is not a StyleDesc key, and at runtime it is rejected: property "zIndex" rejected value 1: unsupported style property, with the rest of the style applied.

Fix

  1. Paint in CSS stacking order: within a stacking context, in-flow non-positioned content first, then positioned descendants with z-index: auto / 0 in tree order, then positive z-index in ascending order; negative z-index below in-flow content.
  2. Support zIndex on positioned elements, creating a stacking context as CSS does. opacity < 1 and transform should create stacking contexts too, if they don't already.
  3. Hit-testing and pointer delivery follow the same order as painting, so a positioned overlay that paints on top also receives the click.

Done means

  • In the repro, "overlay, then page" shows the red overlay, matching Chromium.
  • zIndex is typed on StyleDesc, accepted without a diagnostic, and orders sibling positioned elements, including a negative value painting below in-flow content.
  • Pointer events reach the topmost painted element in both orders.

🤖 Generated with Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind:rendererLayout, paint, input, or renderer behaviorlayer:gpuiVendored GPUI implementationlayer:nativeRust and N-API native bindingpriority:mediumMedium-priority fork work

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions