106 lines
4.6 KiB
Markdown
106 lines
4.6 KiB
Markdown
# CSS Z-Index & Overflow Clipping Debugging
|
|
|
|
## Symptom
|
|
A dropdown, tooltip, or popup renders but is cut off / hidden behind elements below it.
|
|
|
|
## Root Cause (almost always one of these)
|
|
|
|
1. **Parent `overflow: hidden`** — the dropdown's parent container clips it via CSS overflow
|
|
2. **Missing or broken z-index** — the dropdown has no z-index, or its z-index CSS variable is undefined
|
|
3. **Stacking context** — a parent creates a new stacking context (via `position: relative + z-index`, `opacity < 1`, `transform`, `filter`, `will-change`) which limits how high the child can stack
|
|
4. **Parent position / overflow conflicts** — `position: relative` on a grandparent with `overflow: hidden` blocks absolute children even with high z-index
|
|
5. **Sibling DOM ordering** — the header wrapper has no z-index, so its sibling (main content) paints on top by default
|
|
|
|
## Diagnosis Checklist
|
|
|
|
### 1. Check parent overflow
|
|
```bash
|
|
# Search for overflow:hidden on or near the dropdown's container
|
|
search_files("overflow-hidden", file_glob="*.html", path="src/")
|
|
search_files("overflow-hidden", file_glob="*.css", path="src/")
|
|
search_files("overflow.*hidden", file_glob="*.html", path="src/")
|
|
```
|
|
|
|
### 2. Check CSS variable definitions
|
|
```bash
|
|
# If the dropdown uses a CSS variable for z-index, verify it's defined
|
|
# e.g., `z-index: var(--z-critical) !important;` without `--z-critical: N;` in :root = broken
|
|
search_files("--z-", file_glob="*.css", path="src/")
|
|
search_files("var\(--z-", file_glob="*.html", path="src/")
|
|
search_files("var\(--z-", file_glob="*.js", path="src/")
|
|
```
|
|
|
|
A CSS variable referenced but never defined evaluates to `z-index: invalid` — effectively no z-index at all.
|
|
|
|
### 3. Check the element's actual z-index
|
|
In browser dev tools: inspect the dropdown → computed styles → z-index. If it says `invalid` or isn't listed, the variable isn't resolving.
|
|
|
|
### 4. Check the outer wrapper's position and z-index
|
|
Even when the dropdown and its immediate parent have high z-index, the **outermost header wrapper** may lack `position: relative`, allowing the next DOM sibling (main content) to paint on top.
|
|
|
|
Check: Does the `<div>` that wraps the entire header have `position: relative` and `z-index`?
|
|
|
|
## Fixes
|
|
|
|
### Fix 1: Ensure parent overflow is visible
|
|
On the dropdown's immediate positioned parent:
|
|
```css
|
|
overflow: visible !important;
|
|
```
|
|
Or remove `overflow-hidden` from the Tailwind class list if `overflow-visible-important` is already applied.
|
|
|
|
### Fix 2: Define undefined CSS variables
|
|
```css
|
|
:root {
|
|
--z-critical: 999999;
|
|
}
|
|
```
|
|
|
|
### Fix 3: Set z-index on the header card
|
|
```css
|
|
.header-card {
|
|
z-index: 100;
|
|
position: relative;
|
|
}
|
|
```
|
|
This keeps the header above page content, so the dropdown (which is a child) can stack above content below the header.
|
|
|
|
### Fix 4: Remove conflicting Tailwind classes
|
|
If a div has both `overflow-hidden` AND a class that sets `overflow: visible !important`, remove `overflow-hidden` — the `!important` should win in theory, but mobile Safari and some browsers handle this inconsistently.
|
|
|
|
### Fix 5: Set z-index on the outer header wrapper (non-obvious)
|
|
Even with Fix 1-4, the dropdown can be covered by page content. This happens when the **outermost header div** doesn't create a stacking context:
|
|
|
|
```html
|
|
<div class="header-wrapper"> ← NEEDS position:relative + z-index
|
|
<div class="header-card z-100"> ← Fix 3 applied
|
|
<div id="user-dropdown" z-10001> ← high z-index
|
|
</div>
|
|
</div>
|
|
|
|
<div class="main-content"> ← SIBLING of header-wrapper
|
|
... ← paints ON TOP without Fix 5
|
|
</div>
|
|
```
|
|
|
|
**Fix:** Add `position: relative; z-index: 1000;` to the outermost header wrapper div (not just the header-card inside it). A value of 1000 is enough to beat any content z-index below.
|
|
|
|
## Multi-Layer Verification
|
|
|
|
After fixing, check ALL pages that share the same component:
|
|
- Search for the same CSS classes/IDs across all HTML files
|
|
- Check if each page has its own `<style>` block that overrides the shared stylesheet
|
|
- Verify that shared stylesheet changes (e.g., `style.css`) apply to all pages
|
|
|
|
## Common Pattern: Header Dropdown Clipping
|
|
|
|
```
|
|
<div class="header-card overflow-hidden"> ← PROBLEM: clips children
|
|
<div class="user-menu-container relative">
|
|
<div id="user-dropdown">...</div> ← gets clipped
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
Fix: Remove `overflow-hidden` from `header-card`, add `z-index` to it, and ensure the dropdown has a defined high z-index. Add `.user-menu-container { overflow: visible !important; }` for safety. Then add `position: relative; z-index: 1000` to the outer header wrapper to beat sibling DOM ordering.
|