Why Parent Z-index Traps Child Elements?

Parent z-index traps child elements bugs happen when a child has a high z-index but the parent sits inside a lower stacking layer.

CSS Stacking Context Fix

Why Parent Z-index Traps Child Elements?

A parent z-index traps child elements bug happens when a child looks like it should rise above the page, but the parent’s own stacking layer keeps it trapped. The child may have z-index:9999, but if its parent is below another parent, the child cannot escape that parent’s stacking order.

This is why z-index bugs feel so unfair. You raise the child number higher and higher, but nothing changes. The browser is not comparing only the child against the whole page. It is comparing stacking groups. A child can win inside its own parent and still lose against a sibling parent that sits above the entire group.

  • parent z-index
  • child trapped
  • stacking context
  • layer order

Test the parent layer first

Temporarily raise the parent’s z-index, move the child outside that parent, or lower the competing sibling parent. If the child suddenly appears correctly, the issue was a parent z-index trap, not a child z-index value.

Related: Try this in the FrontFixer Live Inspector.

Open Live Inspector

What the bug looks like

A badge, tooltip, dropdown, modal, or menu has a huge z-index but still appears behind another block.

Why it happens

The child is inside a parent stacking group that is lower than a competing parent group.

What usually fixes it

Change the parent layer, move the child to a root layer, or simplify competing stacking contexts.

Why child z-index cannot always escape its parent

Z-index is not one giant scoreboard where the biggest number always wins. The browser paints elements in layers. Some parents form groups, and children are compared inside those groups before the group is compared against other groups.

That means a child with z-index:9999 can still be behind a sibling section with z-index:2 if the child’s parent is only z-index:1. The child is powerful inside the parent, but the parent is still behind the other group.

The correct fix is to identify which element owns the stacking battle. Sometimes the child should stay where it is and the parent should be raised. Sometimes the child should be moved to a root overlay layer. Sometimes the competing sibling should not have a z-index at all.

Child layerControls order inside one parent group.
Parent layerControls where that whole group sits on the page.
Sibling parentCan beat the entire group with a smaller number.
Better mindsetDebug the stacking owner, not just the child number.
Error 1

A child has z-index:9999 but the parent is behind

This is the classic parent trap. The child has a massive number, but its parent is a lower stacking group. A sibling parent with a smaller z-index can still paint above the entire trapped group.

Broken code

Huge child, low parent
.card-a {
  position: relative;
  z-index: 1;
}

.card-a .tooltip {
  position: absolute;
  z-index: 9999;
}

.card-b {
  position: relative;
  z-index: 5;
}

Broken visual result

Child trapped in low group
Parent A z-index 1
Child 9999 trapped
Parent B z-index 5 wins
The child wins inside Parent A, but Parent A loses to Parent B.

Correct code

Raise the correct owner
.card-a {
  position: relative;
  z-index: 10;
}

.card-a .tooltip {
  position: absolute;
  z-index: 2;
}

.card-b {
  position: relative;
  z-index: 5;
}

Fixed visual result

Parent owns the win
Parent A z-index 10
Child only needs 2
Parent B z-index 5 below
When the parent wins the layer battle, the child no longer needs absurd numbers.
Error 2

The child should be global but stays inside a component

Some children are not really component children. A modal, toast, dropdown, or floating menu may need to escape the component entirely. Keeping it inside a lower parent can make it impossible to layer correctly.

Broken code

Global UI inside component
.product-card {
  position: relative;
  z-index: 1;
}

.product-card .modal {
  position: absolute;
  z-index: 9999;
}

Broken visual result

Modal still local
Card parent Modal 9999 but local Badge trapped too
Header / sidebar parent wins
The modal is visually trying to be global while structurally living inside a card.

Correct code

Move global UI to root
.product-card {
  position: relative;
  z-index: 1;
}

.modal-root .modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
}

Fixed visual result

Root layer escapes parent
Card parent Trigger stays here Local UI only
Page chrome below root modal
Modal moved to root overlay layer
Keep the button inside the component, but mount the overlay child where it can win globally.
Error 3

A negative or low parent layer buries the child

A parent with a negative or very low z-index can bury everything inside it. The child may have a higher local value, but the whole parent group is still painted below neighboring content.

Broken code

Parent layer too low
.hero-art {
  position: relative;
  z-index: -1;
}

.hero-art .badge {
  position: absolute;
  z-index: 20;
}

Broken visual result

Whole group is buried
Parent -1 content
Badge hidden
Normal content paints above
The child badge is high inside a parent that is already buried.

Correct code

Avoid burying the parent
.hero-art {
  position: relative;
  z-index: 0;
}

.hero-art .badge {
  position: absolute;
  z-index: 2;
}

Fixed visual result

Parent returns to page
Parent 0 content
Badge visible
Sibling below intended group
Avoid using negative parent z-index as a shortcut for background layering.
Error 4

Overlapping cards fight as parent groups

In card grids, carousels, and hover layouts, a child badge or menu can look trapped because nearby cards are separate parent groups. The hover child may need the hovered parent to rise, not just the child itself.

Broken code

Only child rises
.card {
  position: relative;
}

.card:hover .menu {
  z-index: 999;
}

.card + .card {
  position: relative;
  z-index: 2;
}

Broken visual result

Menu loses to neighbor card
Hovered card
Menu 999
Neighbor card covers the menu
The child menu rises inside the hovered card, but the neighboring parent still wins.

Correct code

Hovered parent rises
.card {
  position: relative;
  z-index: 1;
}

.card:hover {
  z-index: 10;
}

.card:hover .menu {
  z-index: 2;
}

Fixed visual result

Parent group rises
Hovered parent z-index 10
Menu safe
Neighbor below hovered group
Raise the hovered card group so its children can appear above neighboring cards.
Premium patterns

Three production-minded parent z-index patterns

Premium layer systems avoid random huge numbers. They define which parent groups own local layers, which UI escapes to root, and which interactive parent should rise during hover or focus.

Premium code example 1

Component layer tokens
:root {
  --z-base: 0;
  --z-card: 1;
  --z-card-active: 10;
  --z-local-float: 2;
}

.card {
  position: relative;
  z-index: var(--z-card);
}

.card:focus-within,
.card:hover {
  z-index: var(--z-card-active);
}

Premium visual result 1

Local layer tokens
premium
Cards rise as groups

The active parent owns the layer jump, while the child only manages local floating elements.

child menu local 2 active card 10 normal card 1 base page 0
No random 9999 values
Pattern 1 is ideal for card grids, hover menus, product cards, and dashboard tiles.

Premium code example 2

Root escape for global UI
.card {
  position: relative;
  z-index: var(--z-card);
}

.card__trigger {
  position: relative;
}

.overlay-root {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
}

Premium visual result 2

Global escape route
premium
Local trigger, global overlay

The card can stay in its own layer while modal or drawer UI moves to the root overlay layer.

Component parent button local menu
Root layer modal drawer
Pattern 2 is ideal when the child must escape the parent instead of fighting it.

Premium code example 3

Layer audit comments
/* Layer audit:
   - page chrome: 100
   - active component: 200
   - popover root: 500
   - modal root: 1000
*/

.popover-root {
  position: fixed;
  z-index: 500;
}

Premium visual result 3

Layer audit board
premium
Audit the parent before the child

A simple layer map makes it obvious which parent group should move and which child should stay local.

Parent groupowns stack
Child floatlocal only
Root layerglobal UI
Pattern 3 is ideal for design systems where many components create floating UI.

Fast practical rule

If a child element has a huge z-index but still appears behind something, stop raising the child first. Inspect the parent. The parent z-index traps child elements bug is fixed by changing the stacking owner, not by adding another zero to the child.

Debug checklist

  • Inspect the child and confirm it is positioned.
  • Inspect the parent and check whether it has z-index.
  • Compare the parent’s z-index against sibling parent elements.
  • Look for a child with a huge number inside a low parent group.
  • Raise the hovered or active parent when neighboring cards overlap.
  • Move truly global UI to a root overlay layer.
  • Avoid negative parent z-index unless you fully understand the page stack.
  • Use named z-index tokens instead of random values like 999999.
Best first moveRaise the parent temporarily and see if the child appears.
Most common causeThe child is high inside a parent that is low.
Most sneaky causeA neighboring parent group is winning the stack.
Better mindsetZ-index belongs to groups before it belongs to children.

When the child should stay trapped

A trapped child is not always a bug. Local badges, card decorations, image labels, and small hover details should often stay inside their parent. You only need an escape strategy when the child is meant to interact above neighboring parents or the whole page.

That is why the best fix is not always “move everything to the root.” The best fix is to decide whether the child is local UI or global UI.

If it belongs to the component, raise the component parent. If it belongs to the page, move it to a page-level layer.

Why random z-index values make this worse

Random values hide the real structure of the page. A site with 9, 99, 999, and 999999 everywhere becomes harder to debug because nobody knows which number represents a parent group, a local floating child, or a global overlay.

A small layer scale is usually stronger. Normal components, active components, popovers, headers, overlays, and modals should each have a clear purpose.

Final takeaway

A parent z-index traps child elements bug happens because the child is not fighting the whole page by itself. It is fighting inside its parent group, and that parent group may be lower than a competing sibling group.

Fix the layer owner. Raise the parent when the whole component should win. Move the child to a root layer when the child should behave globally. Do not rely on huge child z-index values to escape the wrong parent.

Want more fixes like this?

Browse more CSS z-index, stacking context, overlay, modal, dropdown, tooltip, and responsive layout debugging guides in the FrontFixer library.

Why Is My Fixed Element Behind a Transformed Parent?

Fixed element behind transformed parent bugs happen when transform on an ancestor changes how fixed headers, drawers, modals, or toasts behave.

CSS Positioning Fix

Why Is My Fixed Element Behind a Transformed Parent?

A fixed element behind transformed parent usually means the element is not behaving like a true viewport-level fixed layer anymore. A parent with transform, translate, scale, rotate, or even transform:translateZ(0) can change the containing block and stacking behavior for fixed descendants.

This is different from a normal z-index problem. You can give the fixed child z-index:9999 and it may still appear behind a header, get clipped inside a card, move with a transformed wrapper, or behave like it belongs to the component instead of the viewport. The real problem is the ancestor. The transformed parent quietly changed the world the fixed element lives in.

  • position:fixed
  • transform
  • containing block
  • stacking context

Test the parent transform first

Temporarily remove transform from parent wrappers in DevTools. If the fixed element jumps to the correct viewport position or finally appears above the page, the ancestor transform is the cause.

Related: Try this in the FrontFixer Live Inspector.

Open Live Inspector

What the bug looks like

A fixed modal, drawer, toast, banner, or floating button behaves as if it belongs to a card or wrapper.

Why it happens

A transformed ancestor can create a containing block and stacking context for fixed children.

What usually fixes it

Move global fixed UI outside the transformed wrapper or remove transform from the ancestor.

Why fixed positioning can stop being viewport-level

Many developers expect position:fixed to always attach to the browser viewport. That is usually the mental model, but it can break when a transformed ancestor appears above the fixed element. The browser may treat that transformed ancestor as the fixed element’s containing block.

Once that happens, the fixed element is no longer truly global. It can move with the parent, stay under another page layer, obey the parent’s visual boundary, or appear in the wrong position. The code still says fixed, but the browser is resolving that fixed positioning inside a different context.

This post targets the fixed element behind transformed parent problem specifically. The broader transform z-index article explains stacking context conflicts; this fix focuses on fixed descendants that lose viewport behavior because an ancestor has transform.

Fixed can become localA transformed ancestor can become the reference boundary.
Transform can be invisibletranslateZ(0) may create the bug without a visible movement.
Global UI should be globalModals, toasts, and drawers should not live inside transformed shells.
Better mindsetSeparate animation wrappers from overlay roots.
Error 1

A modal is fixed inside a transformed card

A card may use transform for hover lift, animation, or performance. If a modal is nested inside that transformed card, the modal can stop behaving like a true viewport overlay and remain stuck in the card’s layer world.

Broken code

Fixed inside transformed card
.card {
  transform: translateY(-4px);
}

.card .modal {
  position: fixed;
  inset: 0;
  z-index: 9999;
}

Broken visual result

Fixed trapped inside card world
Site header layer
Transformed card parent
Fixed modal stuck under header
The modal says fixed, but it is still inside the transformed card context.
A high z-index cannot make the modal global if it is mounted inside the transformed card.

Correct code

Modal root outside card
.card {
  transform: translateY(-4px);
}

.modal-root .modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
}

Fixed visual result

Modal escapes parent
Transformed card remains local
Site header layer
Modal root covers the viewport
The modal is mounted outside the transformed card, so fixed is viewport-level again.
Keep transformed cards local and render global modals from a root overlay layer.
Error 2

A fixed bottom bar lives inside an animated app shell

App shells often use transform for page transitions, drawer animations, or GPU acceleration. A fixed bottom bar inside that shell can become local to the shell instead of staying pinned to the viewport.

Broken code

Fixed bar inside transformed shell
.app-shell {
  transform: translateX(0);
}

.app-shell .bottom-bar {
  position: fixed;
  bottom: 0;
}

Broken visual result

Bottom bar becomes local
Transformed app shell
Fixed bar stuck inside shell
The fixed bar follows the transformed shell instead of the viewport.

Correct code

Fixed bar outside shell
.app-shell {
  transform: translateX(0);
}

.bottom-bar-root {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 50;
}

Fixed visual result

Bottom bar belongs to viewport
Animated shell
Viewport fixed bottom bar
Put fixed viewport UI beside the animated shell, not inside it.
Error 3

A mobile drawer is fixed inside a transformed page wrapper

Mobile menus and drawers often use fixed positioning. But if the drawer is inside a page wrapper that uses transform for transitions, it may not cover the screen correctly and can sit behind other interface layers.

Broken code

Drawer inside transformed page
.page {
  transform: translate3d(0, 0, 0);
}

.page .drawer {
  position: fixed;
  inset: 0 0 0 auto;
}

Broken visual result

Drawer cannot fully escape
Drawer attached to transformed page
The drawer is fixed, but it still behaves like part of the transformed page wrapper.

Correct code

Drawer portal outside page
.page {
  transform: translate3d(0, 0, 0);
}

.drawer-root .drawer {
  position: fixed;
  inset: 0 0 0 auto;
  z-index: 80;
}

Fixed visual result

Drawer uses viewport root
Drawer covers viewport layer
Mount mobile drawers in a root layer when the page wrapper is animated.
Error 4

A performance transform traps fixed notifications

Sometimes the transform was not added for design at all. It was added as a performance hack, such as translateZ(0) or will-change:transform. That invisible optimization can still change how fixed descendants behave.

Broken code

Performance hack on parent
.layout {
  transform: translateZ(0);
}

.layout .toast {
  position: fixed;
  top: 24px;
  right: 24px;
}

Broken visual result

Invisible transform trap
Transformed layout
toast local trapped

The toast appears inside the layout world, not above the entire page.

A transform added for performance can still change fixed positioning behavior.

Correct code

Toast root outside layout
.layout {
  transform: translateZ(0);
}

.toast-root {
  position: fixed;
  top: 24px;
  right: 24px;
  z-index: 90;
}

Fixed visual result

Toast root is global
Transformed layout
page clean

The toast root sits outside and above the layout.

Global toast
Keep notification roots outside performance-transformed layout containers.
Premium patterns

Two production-minded fixed layer patterns

Premium fixed positioning separates animated surfaces from viewport-owned overlays. Below are two different visual systems: one for a dashboard shell and one for a mobile drawer architecture.

Premium code example 1

Dashboard shell with overlay roots
.dashboard-shell {
  transform: translateX(var(--page-shift));
}

.overlay-root {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: var(--z-overlay);
}

.overlay-root > * {
  pointer-events: auto;
}

Premium visual result 1

Dashboard layer map
premium
Dashboard shell architecture

The dashboard can animate while overlays stay in a viewport-owned root.

animated shell
content layer
overlay root
modal toast drawer
Pattern 1 is ideal for dashboards, admin panels, apps, and animated page transitions.

Premium code example 2

Mobile drawer outside transformed page
.page-transition {
  transform: translate3d(var(--x), 0, 0);
}

.mobile-drawer-root {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
}

.mobile-drawer {
  position: absolute;
  inset: 0 0 0 auto;
  width: min(360px, 100%);
}

Premium visual result 2

Mobile root stack
premium
Mobile fixed drawer system

The page transition moves below while the drawer root remains viewport-owned.

page transition content
base page transformed transition drawer root fixed viewport safe
Pattern 2 is ideal for mobile menus, off-canvas navigation, app drawers, and page-slide transitions.

Fast practical rule

If a fixed element is behind a transformed parent, stop raising z-index first and inspect the ancestor chain. Remove the transform, move the fixed UI outside that parent, or create a dedicated viewport-level root for modals, drawers, toasts, and floating bars.

Debug checklist

  • Inspect the fixed element that appears behind the page or inside a component.
  • Check every ancestor for transform, translate, scale, or rotate.
  • Look for performance hacks such as translateZ(0).
  • Temporarily remove parent transforms in DevTools.
  • Move global fixed UI outside transformed wrappers.
  • Use a root overlay container for modals, drawers, toasts, and popovers.
  • Keep page transitions separate from overlay ownership.
  • Use z-index tokens only after the layer ownership is correct.
Best first moveRemove the ancestor transform and see if fixed behavior returns.
Most common causeA modal or drawer is mounted inside a transformed card or app shell.
Most sneaky causetransform:translateZ(0) was added as a performance fix.
Better mindsetAnimated surfaces and fixed overlay roots should be separate layers.

When transform is still the right choice

transform is not wrong. It is excellent for animation, hover movement, transitions, drawer motion, and smooth UI effects. The mistake is placing global fixed UI inside the element that is being transformed.

Keep transforms on the visual surface that needs movement. Keep fixed overlays in a stable root that is not part of that transformed surface. That gives you animation without turning fixed positioning into a local component behavior.

A good production rule is simple: if the element should cover the whole viewport or stay pinned to the browser window, mount it near the document root. If the element belongs visually to a card, drawer, or component, it can stay inside that component.

The authority move is not to avoid transform forever. The authority move is to avoid letting transform own the viewport layer.

Why this bug survives desktop review

This bug often survives because the fixed element may look almost correct on a wide desktop screen. The problem becomes obvious when a modal needs to cover the whole page, a mobile drawer opens, a toast should float above the app, or a bottom bar should stay attached to the viewport.

A serious review tests fixed UI inside every animated shell, transformed card, mobile page transition, and GPU-accelerated wrapper. If the element is meant to be global, it should not be mounted inside a transformed parent.

Final takeaway

A fixed element behind transformed parent is usually not fixed by a bigger z-index. The fixed element may be trapped because an ancestor with transform changed its containing block and stacking context.

Remove unnecessary parent transforms, avoid performance transforms on large layout wrappers, and mount global UI in viewport-level roots. That keeps modals, drawers, toasts, and fixed bars truly fixed.

When the bug appears only inside one animated section, do not rewrite every z-index value on the site. Find the transformed ancestor first.

Want more fixes like this?

Browse more CSS positioning, z-index, transform, modal, drawer, and responsive debugging guides in the FrontFixer library.

Why Does filter Create a Stacking Context?

Filter creates stacking context bugs happen when CSS filter effects create a new local layer that traps dropdowns, modals, tooltips, or badges.

CSS Stacking Context Fix

Why Does filter Create a Stacking Context?

Filter creates stacking context problems when a parent uses filter, blur(), brightness(), drop-shadow(), or similar effects and traps its children inside a local layer. A tooltip, dropdown, modal, badge, or menu can have a large z-index and still lose to elements outside that filtered parent.

This bug is easy to miss because filters are usually added for purely visual polish. A card gets a soft blur. A hero image gets brightness control. A product tile gets a drop shadow. A background panel gets a glass effect. Then a child overlay suddenly refuses to rise above a header, neighboring card, sticky bar, or modal layer. The problem is not only the child. The parent effect changed the stacking rules.

  • filter
  • stacking context
  • z-index
  • overlays

Test the filter first

Temporarily remove filter or backdrop-filter from parent wrappers in DevTools. If the overlay suddenly appears above the page, the issue is a stacking context boundary.

Related: Try this in the FrontFixer Live Inspector.

Open Live Inspector

What the bug looks like

A tooltip, dropdown, modal, badge, or menu appears behind another element even with a high z-index.

Why it happens

The filtered parent becomes a local stacking context and paints its children as one group.

What usually fixes it

Apply the filter to a smaller visual layer or move overlays outside the filtered component.

Why a visual filter can change layer behavior

A CSS filter does more than change appearance. The browser often has to render the filtered element and its children as a separate composited group, then apply the effect to that group. That group can behave like its own stacking context.

Once the parent becomes a stacking context, the child overlay does not compete directly with the entire page. It competes inside the filtered group. Then the whole group competes against other page layers. A child with a huge z-index can still lose if the filtered parent is below a header, sibling card, or modal root.

The clean fix is to separate visual effects from overlay ownership. Put the filter on the image, surface, or decorative pseudo-element. Keep the menu, tooltip, modal, or popover in a clean layer that can actually appear above the interface.

The phrase filter creates stacking context is important because this is not the same problem as a normal z-index typo. The filter itself is the clue. When a filtered wrapper owns the floating element, the wrapper becomes the ceiling.

Filter groups childrenThe effect can make the parent and children paint together.
Z-index becomes localThe child can be high inside a low filtered group.
Visual polish can trap UIBlur, brightness, and drop-shadow may affect layers.
Better mindsetFilter the visual shell, not the overlay owner.
Error 1

A modal is inside a filtered card

A product card or feature card may use filter:drop-shadow() or filter:brightness() for polish. If a modal, preview, or expanded detail panel is nested inside that card, the overlay can be trapped by the filtered parent.

Broken code

Modal inside filtered card
.product-card {
  filter: drop-shadow(0 12px 24px rgb(0 0 0 / .2));
}

.product-card .modal {
  position: absolute;
  z-index: 9999;
}

Broken visual result

Filtered parent traps modal
filtered card context
Filtered product card parent
Modal z-index 9999
Header / cart layer wins
The modal is high inside the card, but the filtered card group still loses.
The modal is not truly global while it lives inside the filtered card.

Correct code

Modal rendered outside card
.product-card__media {
  filter: drop-shadow(0 12px 24px rgb(0 0 0 / .2));
}

.modal-root .modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
}

Fixed visual result

Modal moved to root
root overlay layer
Filtered media only
Card stays visual
Modal root covers the interface
The filter stays on the media, while the modal lives in a clean root layer.
Filter the visual piece, not the parent that owns a full-page overlay.
Error 2

A glass panel traps its dropdown

Glassmorphism panels often use backdrop-filter or blur effects. If the same panel owns a dropdown, the menu may be visually trapped by the glass layer and lose to nearby header or modal elements.

Broken code

Dropdown inside glass panel
.glass-panel {
  backdrop-filter: blur(16px);
}

.glass-panel .dropdown {
  position: absolute;
  z-index: 80;
}

Broken visual result

Dropdown trapped in glass
Glass account panel
Profile Dropdown trapped
Outside layer overlaps menu
The dropdown belongs to the blurred panel instead of a clean menu layer.

Correct code

Glass surface separated
.glass-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  backdrop-filter: blur(16px);
}

.dropdown-layer {
  position: absolute;
  z-index: 80;
}

Fixed visual result

Menu stays above glass
Glass visual surface
Profile Surface only
Dropdown layer above panel
Put blur on a pseudo-element or surface, while the dropdown lives above the panel.
Error 3

A filtered image traps a badge

Image cards often use filter:brightness(), grayscale, or contrast changes. If the image wrapper also owns a badge, favorite button, or tooltip, that floating child may be trapped under another sibling card.

Broken code

Badge inside filtered wrapper
.image-card {
  filter: brightness(.75);
}

.image-card .badge {
  position: absolute;
  z-index: 20;
}

Broken visual result

Badge trapped with image
Filtered image wrapper
Sale badge trapped
Next card covers badge
Filtering the whole image card can trap the badge with the image layer.

Correct code

Filter image only
.image-card img {
  filter: brightness(.75);
}

.image-card .badge {
  position: absolute;
  z-index: 20;
}

Fixed visual result

Badge stays independent
Filtered image only
Badge above image
Sibling no longer wins
Apply filters directly to the media element when badges need independent stacking.
Error 4

A filtered parent makes a popover feel random

A parent may use filter for a color treatment, grayscale state, or hover effect. The popover works in other sections but fails inside that one component because the filtered parent quietly changes the layer rules.

Broken code

Popover inside filtered component
.promo-widget {
  filter: saturate(.8);
}

.promo-widget .popover {
  position: absolute;
  z-index: 300;
}

Broken visual result

Works elsewhere, fails here
filtered widget popover stuck
sidebar layer wins
The same popover may work outside this filtered widget, which makes the bug feel inconsistent.
The failure follows the parent context, not just the popover CSS.

Correct code

Popover root separated
.promo-widget__art {
  filter: saturate(.8);
}

.popover-root {
  position: fixed;
  z-index: 300;
}

Fixed visual result

Popover layer is predictable
filtered art content clean
popover root above
The visual treatment stays inside the widget while the popover uses its own layer.
Move reusable popovers to a predictable root layer when components use filter effects.
Premium patterns

Two production-minded filter layer patterns

Premium filter architecture keeps visual effects and overlay behavior separate. Below are two different patterns: one for filtered cards and one for app-wide overlay systems.

Premium code example 1

Filter the surface only
.card {
  position: relative;
}

.card__art {
  filter: brightness(.8) saturate(.9);
}

.card__badge,
.card__tooltip {
  position: absolute;
  z-index: var(--z-floating);
}

Premium visual result 1

Visual effect separated
premium
Filtered card system

The media receives the filter, while badges and tooltips stay in a clean floating layer.

Visual layer filtered art card content
Floating layer tooltip badge
Pattern 1 is ideal for product cards, feature cards, media grids, and hover image treatments.

Premium code example 2

Overlay root outside filter
<section class="filtered-showcase">
  <div class="showcase-art">...</div>
</section>

<div class="overlay-root">
  <div class="popover">...</div>
</div>

.showcase-art {
  filter: blur(2px) brightness(.9);
}

.overlay-root {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
}

Premium visual result 2

Overlay exits filter context
premium
Filtered showcase layout

The showcase can use blur and brightness while the popover sits in a root overlay layer.

filtered showcase art
overlay root above filters
Pattern 2 is ideal for filtered hero sections, glass panels, product showcases, and modal-triggering cards.

Fast practical rule

If filter creates a stacking context, stop raising the child’s z-index and inspect the parent. Put the filter on the smallest visual element possible, or move the floating UI outside the filtered wrapper.

Debug checklist

  • Inspect the overlay that refuses to appear above the page.
  • Check its parents for filter or backdrop-filter.
  • Look for blur(), brightness(), saturate(), and drop-shadow().
  • Temporarily remove filters in DevTools and test the overlay again.
  • Move full-page overlays to a root container when they need to escape.
  • Apply filters to images, art layers, or pseudo-elements instead of wrapper parents.
  • Check glass panels and filtered hero sections carefully.
  • Use named z-index tokens for header, dropdown, modal, tooltip, and toast layers.
Best first moveRemove the parent filter in DevTools and see if the z-index issue disappears.
Most common causeA filtered card owns a modal, tooltip, badge, or dropdown.
Most sneaky causeA decorative drop-shadow() changes the stacking behavior.
Better mindsetFilters belong on visuals, not on overlay-owning parents.

When filter is still the right choice

filter is not wrong. It is useful for image treatments, hover polish, disabled states, glass effects, blur effects, and visual mood. The mistake is applying it to a parent that also needs children to escape into higher layers.

Keep filters on the smallest possible visual layer. Filter the image, the background art, the decorative pseudo-element, or the surface. Keep modals, popovers, dropdowns, and tooltips in predictable overlay layers.

The authority move is to use visual effects without letting them own your layer architecture.

Why this bug survives review

This bug survives because filters are often added late for polish. The component works, then someone adds blur, brightness, grayscale, or drop-shadow and the overlay behavior changes. The bug feels unrelated to the visual change, but it is directly connected.

This is also where cannibalization matters: the target here is not every z-index problem, every transform problem, or every opacity problem. The specific lesson is that filter creates stacking context behavior can appear after a purely visual effect is added to a parent.

A serious review tests overlays inside filtered cards, glass panels, product grids, hero sections, and image wrappers. If a child needs to escape the component, the component should not be the child’s stacking prison.

Final takeaway

filter creates a stacking context when a visual effect turns the parent into a local layer. Children inside that parent can have high z-index values and still lose to elements outside the filtered group.

If the bug appeared after adding blur, brightness, saturate, grayscale, or drop-shadow, debug the filter parent first before rewriting the entire overlay system.

Apply filters to the smallest visual element, separate decorative effects from overlay ownership, and move important floating UI into clean layers. That keeps your design polished without breaking z-index behavior.

Want more fixes like this?

Browse more CSS stacking context, z-index, filter, modal, tooltip, and responsive debugging guides in the FrontFixer library.

Why Does opacity Break Z-index?

Opacity break z-index bugs happen when an element with opacity below 1 creates a stacking context that traps children inside a local layer.

CSS Stacking Context Fix

Why Does opacity Break Z-index?

Opacity break z-index issues usually happen because opacity values below 1 create a new stacking context. That means a tooltip, dropdown, modal, badge, or toast inside a faded parent can get trapped below elements outside that parent, even when the child has a large z-index.

This bug is sneaky because opacity feels like a visual property only. You lower opacity to fade a card, disable a menu, dim a section, or animate a component. Then a child popup suddenly refuses to appear above a header, overlay, sticky bar, or nearby card. The child z-index is not necessarily wrong. It may simply be competing inside the wrong local stacking context.

  • opacity
  • z-index
  • stacking context
  • overlays

Test the faded parent first

Temporarily change parent opacity back to 1 in DevTools. If the tooltip, modal, dropdown, or toast jumps to the correct layer, the problem is an opacity stacking context.

Related: Try this in the FrontFixer Live Inspector.

Open Live Inspector

What the bug looks like

A tooltip, menu, modal, or badge stays behind another element even with a large z-index.

Why it happens

A parent with opacity below 1 becomes a new stacking context and traps children.

What usually fixes it

Do not fade the parent that owns overlays. Fade an inner visual layer or use a separate overlay root.

Why opacity changes the stacking rules

opacity looks harmless because it does not move the element, change its size, or visibly alter the layout flow. But when opacity is less than 1, the browser has to paint that element and its children together as a group. That group becomes a stacking context.

Once that happens, the children inside the faded parent do not compete directly with the rest of the page. They compete inside their parent group. Then the entire faded group competes against other page layers. A child with z-index:9999 can still lose if the faded parent group is underneath another stacking context.

The clean solution is not to keep raising z-index. The clean solution is to decide what should be faded and what should remain free. Fade the card surface, background, or disabled visual state. Do not trap important overlays inside the faded parent.

Opacity groups childrenThe browser paints the parent and children as one local layer.
Z-index becomes localThe child can be high inside a low parent group.
Fades can trap UIDisabled menus and animated cards often create this bug.
Better mindsetFade the visual shell, not the overlay owner.
Error 1

A tooltip is inside a faded card

The most common version happens when a card uses opacity for a disabled, loading, hover, or inactive state. A tooltip or badge inside the card receives a huge z-index, but it still cannot rise above elements outside the faded card.

Broken code

Tooltip inside opacity parent
.card.is-muted {
  opacity: .75;
}

.card .tooltip {
  position: absolute;
  z-index: 9999;
}

Broken visual result

High z-index trapped
opacity parent
Faded card creates local layer
Tooltip z-index 9999
Header layer still wins
The tooltip is high inside the card, but the faded card group is still lower.
The tooltip is not weak. It is trapped inside the opacity stacking context.

Correct code

Fade only inner surface
.card__surface.is-muted {
  opacity: .75;
}

.tooltip {
  position: absolute;
  z-index: 40;
}

Fixed visual result

Tooltip stays free
separated layer
Faded surface only
Header layer
Tooltip is outside the faded surface and can appear above
The visual fade stays on the card surface, not on the overlay owner.
Apply opacity to the visual part, not to the parent that controls floating UI.
Error 2

A disabled menu fades the dropdown owner

A disabled or inactive navigation group may use opacity to look muted. If the dropdown still exists inside that faded group, the menu can be trapped below nearby header layers, even though the dropdown itself has a z-index.

Broken code

Dropdown inside muted group
.nav-group.is-muted {
  opacity: .6;
}

.nav-group .dropdown {
  position: absolute;
  z-index: 80;
}

Broken visual result

Dropdown inherits trap
muted nav group
Header actions
Muted nav group
Products Pricing
Dropdown stuck under stronger header layer
The dropdown belongs to the faded group, so it cannot freely cover the header system.

Correct code

Fade label, not menu owner
.nav-label.is-muted {
  opacity: .6;
}

.dropdown {
  position: absolute;
  z-index: 80;
}

Fixed visual result

Dropdown remains independent
stable nav group
Header actions
Nav owner stays normal
Muted label only Dropdown owner clean
Dropdown above nav content
Keep opacity on the label or surface, while the dropdown owner stays in a clean layer.
Error 3

A dimmed page fades the entire app shell

Some interfaces dim the whole page by applying opacity to the app shell when a modal or loading state appears. That can trap toasts, drawers, and secondary overlays inside the faded shell instead of letting them sit above the page.

Broken code

Whole shell faded
.app-shell.is-dimmed {
  opacity: .45;
}

.toast {
  position: fixed;
  z-index: 1000;
}

Broken visual result

Toast trapped in dimmed shell
Toast faded with app shell
The toast is fixed, but it is still inside the faded app shell group.
Fading the whole app shell can accidentally fade and trap UI that should stay above it.

Correct code

Use a dim overlay layer
.page-dim {
  position: fixed;
  inset: 0;
  background: rgb(15 23 42 / .55);
  z-index: 40;
}

.toast-root {
  position: fixed;
  z-index: 60;
}

Fixed visual result

Toast above dim overlay
Toast stays clear above dim layer
Separate dim overlay behind toast
The page is dimmed by an overlay, while toast lives in its own root layer.
Use a dedicated dim overlay instead of lowering opacity on the whole app shell.
Error 4

A fade animation keeps the wrong stacking context

A fade animation may temporarily set opacity below 1. During that state, the element creates a stacking context. If a menu or overlay appears while the fade state is active, it can be trapped unexpectedly.

Broken code

Fade wrapper owns overlay
.panel.is-entering {
  opacity: .98;
}

.panel .popover {
  position: absolute;
  z-index: 300;
}

Broken visual result

Almost invisible opacity trap
opacity .98 wrapper
Popover inside fade wrapper
Nearby layer wins
Even opacity .98 can create the layer boundary that changes stacking behavior.
Tiny opacity changes can still create a stacking context while animations are active.

Correct code

Fade content, keep popover root clean
.panel__content.is-entering {
  opacity: .98;
}

.popover-root {
  position: fixed;
  z-index: 300;
}

Fixed visual result

Popover root stays clean
content fade only
Popover root above animated content
The animated content can fade without owning the popover layer.
Animate the content layer, but render floating UI in a clean root layer.
Premium patterns

Two production-minded opacity layer patterns

Premium opacity handling separates visual fading from overlay ownership. Below are two different production patterns: one for faded component surfaces and one for full-page dim states.

Premium code example 1

Fade surface, not overlay owner
.card {
  position: relative;
}

.card__surface.is-muted {
  opacity: .65;
}

.card__tooltip {
  position: absolute;
  z-index: var(--z-tooltip);
}

Premium visual result 1

Surface fade stays isolated
premium
Card surface architecture

The faded surface and the tooltip owner are separate responsibilities.

Visual state faded surface card content
Floating UI tooltip layer safe token
Pattern 1 is ideal for muted cards, disabled products, hover fades, and component-level tooltips.

Premium code example 2

Dim layer plus overlay roots
:root {
  --z-dim: 40;
  --z-modal: 60;
  --z-toast: 80;
}

.page-dim {
  position: fixed;
  inset: 0;
  background: rgb(15 23 42 / .55);
  z-index: var(--z-dim);
}

.modal-root { z-index: var(--z-modal); }
.toast-root { z-index: var(--z-toast); }

Premium visual result 2

Dim state has its own layer
premium
Full-page overlay system

The page is dimmed by a separate layer while modal and toast roots stay above it.

dim layer behind overlays
modal + toast roots above dim
Pattern 2 is ideal for apps with modals, drawers, command palettes, loading states, and toast notifications.

Fast practical rule

If opacity breaks z-index, stop increasing the child’s z-index and inspect the parent. Any opacity below 1 can create a local stacking context. Fade the visual surface, not the parent that owns the floating UI.

Debug checklist

  • Inspect the element that refuses to appear above the page.
  • Check every parent for opacity below 1.
  • Look for fade animations, loading states, disabled states, and muted wrappers.
  • Temporarily set parent opacity to 1 in DevTools.
  • Move tooltips, modals, toasts, and dropdowns outside faded parents.
  • Fade an inner surface or pseudo-element instead of the overlay owner.
  • Use a separate dim overlay instead of applying opacity to the app shell.
  • Define named z-index tokens for dim, modal, tooltip, and toast layers.
Best first moveChange parent opacity to 1 and see whether the layer problem disappears.
Most common causeA faded card or muted nav group owns a tooltip or dropdown.
Most sneaky causeOpacity .98 during an animation can still create the problem.
Better mindsetOpacity is visual, but it can also change the stacking architecture.

When opacity is still the right choice

opacity is not wrong. It is useful for disabled states, transitions, skeleton loading, fades, dimmed cards, and subtle UI emphasis. The mistake is using opacity on a parent that also owns floating UI that needs to escape.

Use opacity on the smallest visual layer possible. A card surface can fade. A label can fade. A disabled content area can fade. But the tooltip, modal, dropdown, or toast root should stay in a clean stacking context.

The authority move is to separate visual fading from layer ownership.

Why this bug survives review

This bug survives because opacity changes are often state-based. The layout may work in the normal state and break only during a disabled, loading, muted, hover, or transition state. That makes the bug feel random.

A serious review tests overlays while parent components are loading, disabled, faded, animated, and dimmed. If an overlay must escape the component, it should not live inside the component’s faded layer.

Final takeaway

opacity breaks z-index when a parent with opacity below 1 creates a stacking context. The child can have a huge z-index and still lose because it is trapped inside that faded parent group.

Use opacity on inner visual surfaces, use separate dim layers for full-page states, and move overlays to clean roots when they need to escape. That keeps fades beautiful without turning z-index into a guessing game.

Want more fixes like this?

Browse more CSS stacking context, z-index, opacity, modal, tooltip, and responsive debugging guides in the FrontFixer library.

Why Is My Tooltip Hidden Behind Other Elements?

Tooltip hidden behind elements problems usually happen when the tooltip is clipped by overflow, trapped in a stacking context, placed under a higher z-index element, or positioned relative to the wrong parent.

Tooltip Layer Fix

Why is my tooltip hidden behind other elements?

A tooltip can be coded correctly and still appear behind a card, header, image, dropdown, modal, or nearby section. The problem is rarely the text itself. It is usually a layout-layer problem: overflow:hidden, low z-index, stacking context, transformed parents, or a tooltip that is positioned inside the wrong container.

  • Tooltip z-index
  • Overflow clipping
  • Stacking context
  • Position absolute

What the bug looks like

The tooltip appears cut in half, shows under a card, disappears behind a header, or cannot escape its container.

Why it happens

Tooltips are floating UI, but they are often placed inside normal layout containers that clip or trap them.

What usually fixes it

Remove clipping from the parent, raise the tooltip layer, avoid stacking traps, or render the tooltip in a safer page-level layer.

Error 1

The tooltip is clipped by overflow:hidden

This is one of the most common tooltip bugs. The card needs rounded corners or image cropping, so it uses overflow:hidden. But the tooltip also lives inside that card, so the parent cuts it off.

Broken code

Parent clips tooltip
.card {
  position: relative;
  overflow: hidden;
}

.tooltip {
  position: absolute;
  bottom: 100%;
  z-index: 20;
}

Broken visual result

Tooltip gets cut off
Product card

The tooltip is inside a parent that clips anything outside the card.

Info This tooltip needs to escape the card, but overflow cuts it.
A higher z-index cannot fix clipping caused by parent overflow.

Correct code

Visible floating area
.card {
  position: relative;
  overflow: visible;
}

.tooltip {
  position: absolute;
  bottom: calc(100% + 8px);
  z-index: 20;
}

Fixed visual result

Tooltip can escape
Product card

The tooltip can float outside the card because the parent no longer clips it.

Info This tooltip can now appear above the card.
If the component must crop images, put the cropped image in its own wrapper instead of clipping the whole card.
Error 2

The tooltip has a lower z-index than nearby content

If the tooltip is not clipped but still appears behind another card, header, image, or panel, the problem is layer order. The tooltip needs to be above the nearby interactive content.

Broken code

Low tooltip layer
.tooltip {
  position: absolute;
  z-index: 2;
}

.feature-card {
  position: relative;
  z-index: 10;
}

Broken visual result

Another element paints above it
Pricing option

The tooltip is visible, but another content layer overlaps it.

Info Low z-index tooltip hidden by a card.
Feature card This layer is above the tooltip.
The tooltip is not clipped. It is simply behind a higher layer.

Correct code

Tooltip layer wins
.tooltip {
  position: absolute;
  z-index: 100;
}

.feature-card {
  position: relative;
  z-index: 1;
}

Fixed visual result

Tooltip above nearby content
Pricing option

The tooltip has a deliberate layer above nearby cards.

Info Tooltip layer is now above the card.
Feature card The card no longer covers the tooltip.
Use a controlled z-index scale instead of guessing random numbers.
Error 3

A transformed parent creates a stacking context trap

A tooltip can have z-index:9999 and still lose if it is inside a parent stacking context. Common triggers include transform, filter, opacity, isolation, and positioned parents with z-index.

Broken code

Tooltip trapped
.card {
  transform: translateZ(0);
  z-index: 1;
}

.card .tooltip {
  position: absolute;
  z-index: 9999;
}

Broken visual result

Huge z-index still loses
Transformed card

The tooltip is inside a lower stacking context.

Info Even z-index 9999 cannot escape this parent.
Page layer
z-index only competes inside the stacking context where the element lives.

Correct code

Move tooltip to safe layer
<body>
  <main class="page">...</main>
  <div class="tooltip-layer">...</div>
</body>

Fixed visual result

Tooltip escapes the trap
Transformed card

The component can stay transformed.

Info
Page layer
Tooltip is rendered in a safer page-level layer.
For complex UI, render floating elements in a dedicated layer outside transformed components.
Error 4

The tooltip is positioned relative to the wrong parent

position:absolute uses the nearest positioned ancestor. If the trigger is inside one element but the tooltip is positioned relative to another, the tooltip can appear in the wrong place and hide behind unrelated content.

Broken code

No local anchor
.tooltip-wrap {
  /* missing position: relative */
}

.tooltip {
  position: absolute;
  bottom: 100%;
  left: 0;
}

Broken visual result

Tooltip floats from wrong place
Tooltip anchored to the wrong ancestor.
Settings row

The trigger is here, but the tooltip is not anchored locally.

Info
Other content The tooltip can collide with this.
The tooltip may look hidden because it is not positioned where you think it is.

Correct code

Local tooltip anchor
.tooltip-wrap {
  position: relative;
  display: inline-flex;
}

.tooltip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  z-index: 100;
}

Fixed visual result

Tooltip anchored correctly
Settings row

The tooltip is positioned relative to the trigger area.

Info Tooltip is anchored to the right local parent.
Other content No longer fighting the tooltip.
Use a local wrapper for simple tooltips and a page-level layer for complex floating UI.
Premium pattern

A production-minded tooltip layer pattern

A stronger tooltip system separates normal layout from floating UI. Simple tooltips can use a local relative wrapper, but complex tooltips should use a dedicated high-level layer so cards, grids, overflow, and transforms do not trap them.

Premium code

Safe tooltip system
:root {
  --layer-tooltip: 1000;
}

.tooltip-anchor {
  position: relative;
  display: inline-flex;
}

.tooltip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  z-index: var(--layer-tooltip);
  transform: translateX(-50%);
  max-width: min(260px, calc(100vw - 32px));
}

.card-media {
  overflow: hidden;
}

.card-body {
  overflow: visible;
}
/* For complex apps */
.tooltip-layer {
  position: fixed;
  inset: 0;
  z-index: var(--layer-tooltip);
  pointer-events: none;
}

.tooltip-layer .tooltip {
  position: absolute;
  pointer-events: auto;
}

Premium visual result

Floating layer, predictable result
Premium card

The card layout stays clean, and the tooltip has a predictable layer.

Info
Clean tooltip It floats above cards, grids, headers, and nearby sections.
Premium tooltip CSS does not depend on luck. It uses a clear anchor, safe overflow, and a controlled layer.

Fast practical rule

If a tooltip is hidden behind other elements, do not only increase z-index. First check whether the parent is clipping the tooltip with overflow:hidden. Then check stacking context, positioned ancestors, and whether the tooltip should live in a higher page-level layer.

Debug checklist

  • Inspect the tooltip and confirm it has a real position, usually position:absolute or position:fixed.
  • Check the parent container for overflow:hidden, overflow:auto, or overflow:clip.
  • Temporarily set the parent to overflow:visible to see if clipping is the cause.
  • Compare the tooltip z-index with nearby cards, headers, overlays, and dropdowns.
  • Look for stacking context creators like transform, filter, opacity, isolation, and positioned parents.
  • Make sure the tooltip is positioned relative to the intended wrapper.
  • Avoid placing app-level floating UI inside cards, sliders, or overflow-heavy layout wrappers.
  • Use a simple layer scale for tooltip, dropdown, header, modal, and overlay values.
Best first move Toggle parent overflow in DevTools. If the tooltip appears, you found the clipping parent.
Most common cause A card uses overflow:hidden for rounded corners and accidentally clips the tooltip.
Most sneaky cause A transformed parent traps the tooltip in a lower stacking context.
Better mindset Tooltips are floating UI. They should not be trapped by normal content layout.

Final takeaway

A tooltip hidden behind other elements is usually a layer and containment problem, not a text problem. The tooltip may be clipped by overflow, trapped in a stacking context, placed under another z-index layer, or positioned relative to the wrong parent.

Start with overflow, then inspect stacking context, then fix the tooltip’s layer strategy. For simple UI, a local relative wrapper can work. For complex UI, a dedicated tooltip layer is safer and easier to debug.

Want more fixes like this?

Browse more z-index, tooltip, overlay, and CSS debugging guides in the FrontFixer library.

Why Is My Modal Behind the Overlay?

Modal behind overlay problems usually happen when the overlay has a higher z-index than the modal, the modal is trapped inside a stacking context, or the modal and overlay are placed in the wrong HTML structure.

Modal z-index Fix

Why is my modal behind the overlay?

A modal behind overlay bug is one of the most confusing CSS problems because the modal exists, the overlay exists, and the code looks almost right. But visually, the dark overlay sits above the modal, the popup looks faded, or the modal cannot be clicked. The real issue is usually layer order: z-index, stacking context, position, transform, or where the modal is placed in the HTML.

  • Modal z-index
  • Overlay layer
  • Stacking context
  • Click blocking

What the bug looks like

The modal appears dim, sits under the dark background, cannot be clicked, or looks like it opens behind the page.

Why it happens

The overlay and modal are not in the right stacking order, or the modal is trapped inside a lower stacking context.

What usually fixes it

Put the overlay and modal in one predictable layer system, then make the modal layer higher than the overlay.

Error 1

The overlay has a higher z-index than the modal

This is the simplest version of the bug. The overlay is placed above the page, but the modal is not placed above the overlay. The result is a popup that looks hidden, darkened, or unclickable.

Broken code

Overlay wins
.overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
}

.modal {
  position: fixed;
  z-index: 50;
}

Broken visual result

Modal is under overlay
Page content
behind Modal

The popup is lower than the overlay layer.

The modal exists, but the overlay paints above it and steals the visual focus.

Correct code

Modal wins
.overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
}

.modal {
  position: fixed;
  z-index: 1001;
}

Fixed visual result

Modal above overlay
Page content
above Modal

The popup is now above the overlay layer.

The modal layer must be higher than the overlay layer.
Error 2

The modal is trapped inside a stacking context

A modal can have a huge z-index and still lose if it is inside a parent stacking context. A transformed parent, filtered parent, or positioned parent with its own z-index can trap the modal below the page overlay.

Broken code

Trapped modal
.card {
  transform: translateZ(0);
  z-index: 1;
}

.card .modal {
  position: fixed;
  z-index: 9999;
}

Broken visual result

Huge z-index still loses
Transformed parent
trapped Modal

The modal is inside a lower parent context.

A huge z-index cannot escape a parent stacking context.

Correct code

Page-level modal
<body>
  <main class="page">...</main>

  <div class="overlay"></div>
  <div class="modal">...</div>
</body>

Fixed visual result

Modal escaped the trap
Page card
page level Modal

The modal is a sibling of the overlay at page level.

Place app-level overlays and modals near the end of the body or in a portal/root layer.
Error 3

The modal has z-index, but no useful positioning

Developers often add z-index to a modal and expect it to jump above everything. But if the element is not positioned or not participating in a stacking order that accepts z-index, the value may not solve the layer problem.

Broken code

z-index without position
.overlay {
  position: fixed;
  inset: 0;
  z-index: 20;
}

.modal {
  z-index: 100;
}

Broken visual result

Layer rule incomplete
Page content
incomplete Modal

The modal layer is not defined clearly.

z-index alone is not a complete modal positioning system.

Correct code

Positioned modal layer
.overlay {
  position: fixed;
  inset: 0;
  z-index: 20;
}

.modal {
  position: fixed;
  inset: auto;
  z-index: 30;
}

Fixed visual result

Position and layer agree
Page content
defined Modal

The modal is positioned and layered above the overlay.

For modals, define both placement and layer order explicitly.
Error 4

The HTML order makes the overlay cover the modal

When two positioned elements are in the same stacking level, later elements can paint above earlier elements. If the overlay is inserted after the modal and both use similar z-index values, the overlay may cover the modal.

Broken code

Overlay after modal
<div class="modal">...</div>
<div class="overlay"></div>

Broken visual result

DOM order fights layer order
Page content
covered Modal

The overlay is inserted after this layer.

When layers are not explicit, DOM order can make the overlay paint above the modal.

Correct code

Explicit layer order
<div class="overlay"></div>
<div class="modal">...</div>

Fixed visual result

Overlay below modal
Page content
top layer Modal

The modal is inserted and layered above the overlay.

Use clear z-index values and sensible HTML order for overlay systems.
Premium pattern

A production-minded modal layer pattern

A reliable modal system uses named layer values, keeps modal and overlay close together in the DOM, and avoids placing modals inside transformed cards, sliders, headers, or small layout wrappers.

Premium code

Predictable modal stack
<div class="modal-root">
  <div class="modal-overlay"></div>

  <section class="modal-dialog" role="dialog" aria-modal="true">
    ...
  </section>
</div>
:root {
  --layer-overlay: 1000;
  --layer-modal: 1010;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--layer-overlay);
  background: rgba(15, 23, 42, .64);
}

.modal-dialog {
  position: fixed;
  left: 50%;
  top: 50%;
  z-index: var(--layer-modal);
  transform: translate(-50%, -50%);
  width: min(100% - 32px, 480px);
}

Premium visual result

Modal system, not z-index guessing
Page content
clean layer Modal dialog

The overlay and dialog have predictable, named layers.

Premium modal CSS avoids random z-index numbers and makes the layer order obvious.

Fast practical rule

If your modal is behind the overlay, compare the overlay layer and the modal layer first. Then inspect the modal’s parents for stacking context traps like transform, opacity, filter, isolation, and positioned wrappers.

Debug checklist

  • Inspect the overlay and modal in DevTools and compare their computed z-index values.
  • Make sure both overlay and modal have a real position, usually position:fixed.
  • Keep the modal z-index higher than the overlay z-index.
  • Check whether the modal is inside a transformed, filtered, or positioned parent.
  • Move app-level modals near the end of the document or into a dedicated modal root.
  • Avoid random values like z-index:999999; use a small layer scale instead.
  • Check whether the overlay is stealing clicks with pointer-events.
  • Verify that the modal is not inside a header, slider, card, or overflow-hidden wrapper.
Best first move Temporarily lower the overlay z-index. If the modal appears, the issue is layer order.
Most common cause The overlay has a higher z-index than the modal.
Most sneaky cause A parent with transform traps the modal inside a lower stacking context.
Better mindset A modal should be a page-level layer, not a child of whatever section opened it.

Final takeaway

A modal behind overlay problem is usually not fixed by throwing a bigger z-index at the modal. The real fix is to understand which stacking context the modal belongs to and whether the overlay is above it.

Place the overlay and modal in a predictable page-level layer, give the overlay a lower layer than the modal, and avoid trapping the modal inside transformed or overflow-heavy parents.

Want more fixes like this?

Browse more z-index, overlay, and CSS debugging guides in the FrontFixer library.

Why Is My Sticky Header Not Staying on Top?

Sticky header not staying on top problems usually happen when the header is missing a top value, trapped inside the wrong scroll container, placed behind content by z-index, or affected by overflow and stacking context rules.

Sticky Header Fix

Why is my sticky header not staying on top?

A sticky header can work for a few pixels, then disappear. It can stick inside the wrong parent, slide behind a hero section, or stop staying on top when another element creates a new stacking context. The fix is not just “add z-index.” You need to check the sticky offset, parent overflow, scroll container, and stacking order together.

  • position sticky
  • z-index
  • overflow hidden
  • stacking context

What the bug looks like

The header scrolls away, sticks only inside one section, sits behind content, or stops being clickable on mobile.

Why it happens

Sticky behavior depends on the nearest scroll container, the sticky offset, and the stacking context around the header.

What usually fixes it

Put the header high in the HTML, add a real top value, avoid trapping parent overflow, and use a deliberate z-index.

Error 1

position:sticky is missing the sticky offset

position:sticky does not mean “always stay at the top.” It means the element behaves normally until it reaches a specified offset. Without top:0, the browser may have no useful sticky boundary.

Broken code

Missing top value
.site-header {
  position: sticky;
  z-index: 10;
}

Broken visual result

Header scrolls away
not sticking
Brand
Hero section
Content keeps scrolling
The header has sticky positioning, but no clear top point where it should stick.

Correct code

Sticky offset added
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
}

Fixed visual result

Header knows where to stick
Brand
Hero section
Content scrolls under the header
top:0 tells the browser the exact point where the header should become sticky.
Error 2

A parent with overflow is trapping the sticky header

Sticky elements stick relative to their nearest scrolling ancestor. If the header is inside a wrapper with overflow:hidden, overflow:auto, or a short height, the header may only stick inside that wrapper instead of the page.

Broken code

Overflow trap
.page-shell {
  height: 320px;
  overflow: hidden;
}

.site-header {
  position: sticky;
  top: 0;
}

Broken visual result

Sticky is trapped
parent overflow
Brand
Wrapper area
The header cannot stick beyond this parent
The sticky header is not broken. It is obeying the wrong container.

Correct code

Header outside overflow wrapper
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
}

.page-shell {
  overflow: visible;
}

Fixed visual result

Sticky follows the page
Brand
Normal page content
No overflow trap
Keep the sticky header outside unnecessary overflow wrappers when it must stick for the whole page.
Error 3

The header is sticky, but it is behind other content

A header can stay at the top and still look broken if a hero, card, dropdown, or transformed section paints above it. In that case, the issue is stacking order, not sticky behavior.

Broken code

Low z-index
.site-header {
  position: sticky;
  top: 0;
  z-index: 1;
}

.hero-card {
  position: relative;
  z-index: 10;
}

Broken visual result

Content covers header
behind content
Brand
Hero card

This card paints above the sticky header because its z-index wins.

More page content
The sticky header is there, but another layer is visually covering it.

Correct code

Deliberate header layer
.site-header {
  position: sticky;
  top: 0;
  z-index: 1000;
}

.hero-card {
  position: relative;
  z-index: 1;
}

Fixed visual result

Header stays above content
above content
Brand
Hero card

The content keeps its layer, but the header has the higher page-level layer.

More page content
Use a consistent z-index scale for headers, menus, overlays, and content cards.
Error 4

The sticky header is inside the wrong HTML structure

A site-wide sticky header should usually live near the top of the page structure, not inside a hero section, card, slider, or small wrapper. If its parent ends, the sticky behavior ends with it.

Broken code

Header nested too deep
<section class="hero">
  <header class="site-header">...</header>
  <div class="hero-content">...</div>
</section>

<main>...</main>

Broken visual result

Sticky ends with section
wrong parent
Brand
Hero wrapper
Main content after hero
The header cannot stay sticky for the full page if it belongs to a short section.

Correct code

Header at page level
<header class="site-header">...</header>

<main>
  <section class="hero">...</section>
  <section class="content">...</section>
</main>

Fixed visual result

Header controls the page
Brand
Hero section
Content section
A page-level header has a page-level sticky boundary.
Premium pattern

A production-minded sticky header pattern

A reliable sticky header pattern uses a page-level header, a clear sticky offset, a deliberate z-index layer, and avoids placing the header inside wrappers that create scroll or stacking traps.

Premium code

Sticky header system
<header class="site-header">
  <a class="logo" href="/">Brand</a>
  <nav class="main-nav">...</nav>
</header>

<main class="site-main">
  ...
</main>
:root {
  --header-layer: 1000;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: var(--header-layer);
  background: rgba(255,255,255,.96);
  border-bottom: 1px solid #e5e7eb;
}

.site-main {
  min-width: 0;
}

html {
  scroll-padding-top: 80px;
}

Premium visual result

Predictable sticky layer
Brand
Hero section
Content section
Premium sticky headers are not magical. They are page-level elements with clean parents and predictable layers.

Fast practical rule

If a sticky header is not staying on top, do not start by throwing bigger z-index values at it. First confirm top:0, then check parent overflow, then check whether another element created a stacking context above the header.

Debug checklist

  • Check that the header has position:sticky and a real offset like top:0.
  • Inspect every parent of the header for overflow:hidden, overflow:auto, or overflow:scroll.
  • Move the header outside short wrappers, hero sections, sliders, and cards when it should stick for the whole page.
  • Give the header a deliberate page-level z-index, such as z-index:1000.
  • Check whether another element has a higher z-index than the header.
  • Look for stacking context creators like transform, filter, opacity, isolation, or positioned parents.
  • Test mobile breakpoints to make sure the header is not switched back to position:static.
  • If anchor links hide under the header, add scroll-padding-top or scroll-margin-top.
Best first moveAdd or confirm top:0. Sticky needs an offset to know when to stick.
Most common causeA parent wrapper with overflow changes the sticky boundary.
Most misleading causeThe header is sticky, but content paints above it because of z-index or stacking context.
Better mindsetSticky headers need structure, not random z-index numbers.

Final takeaway

A sticky header not staying on top is usually not one single CSS mistake. It is usually a combination of sticky offset, parent overflow, z-index, stacking context, and HTML structure.

Start with top:0, then inspect the parent containers, then fix the stacking order. Once the header is a clean page-level element with a deliberate layer, it becomes predictable on desktop and mobile.

Want more fixes like this?

Browse more CSS, header, and responsive debugging guides in the FrontFixer library.

Why Is My Dropdown Getting Cut Off?

Dropdown getting cut off problems usually happen when a parent clips overflow, a stacking context traps the menu, or the dropdown is rendered inside a wrapper that was never meant to let children escape.

Dropdown Fix

Why is my dropdown getting cut off even with z-index?

If your dropdown menu opens but gets clipped, hidden, or cut in half, the real problem is usually not that your z-index number is too small. Most dropdown bugs come from overflow:hidden, overflow:auto, a clipped parent, a new stacking context, or a menu rendered inside the wrong part of the DOM.

  • Dropdown clipping
  • Overflow traps
  • Stacking contexts
  • Real UI debugging

What the bug looks like

The dropdown opens, but only part of the menu appears. It may be cut at the bottom of a card, hidden inside a table wrapper, or trapped behind another section.

Why it happens

Dropdowns need visual escape space and correct layering. If a parent clips overflow or creates a new layer boundary, the menu cannot behave like a free floating surface.

What usually fixes it

First separate clipping from layering. If the menu is physically cut off, fix overflow. If it appears behind another element, fix stacking context and z-index.

The mistake: treating every dropdown bug like a z-index bug

A dropdown can disappear for two very different reasons. It can be behind another element, which is a layering issue. Or it can be physically clipped by its parent, which is an overflow issue. A giant z-index:999999 only helps with some layering problems. It does not let a child escape a parent that is cutting visual overflow.

That is why dropdown bugs feel so frustrating. The menu looks like it should float above the page, but the browser still respects the boundaries created by the surrounding layout.

Error 1

Parent overflow is clipping the dropdown

This is the classic dropdown trap. The menu has position:absolute and a huge z-index, but one parent has overflow:hidden. The parent becomes a visual box cutter. The dropdown cannot draw outside that box.

Broken code

Clipped parent
.card {
  position: relative;
  overflow: hidden;
}

.dropdown-menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 9999;
}

Broken visual result

Cut by parent
Options ▾
parent edge

The menu exists, but the parent cuts it off before the full dropdown can become visible.

Correct code

Visible overflow
.card {
  position: relative;
  overflow: visible;
}

.dropdown-menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 20;
}

Fixed visual result

Menu can escape
Options ▾

Once the parent is no longer clipping the menu, the dropdown can render outside the trigger box.

Error 2

The dropdown is hidden behind another section

This is a real z-index problem, but only after you confirm the menu is not being clipped. If the dropdown is fully visible but appears underneath a neighboring section, header, card, or banner, the menu is losing the stacking battle.

Broken code

Wrong layer
.nav {
  position: relative;
  z-index: 1;
}

.next-section {
  position: relative;
  z-index: 5;
}

.dropdown-menu {
  position: absolute;
  z-index: 2;
}

Broken visual result

Behind section
Next section is above

The menu is not cut by overflow. It is losing against another positioned layer.

Correct code

Higher context
.nav {
  position: relative;
  z-index: 50;
}

.dropdown-menu {
  position: absolute;
  z-index: 60;
}

Fixed visual result

Menu wins layer

The dropdown belongs to a higher positioned context, so it can sit above the next section.

Error 3

A stacking context traps the menu

A dropdown can have a huge z-index and still lose if it is inside a parent stacking context. Properties like transform, filter, opacity, isolation:isolate, and sometimes will-change can create a layer boundary. The dropdown then competes only inside that boundary.

Broken code

Trapped context
.header-wrap {
  transform: translateZ(0);
}

.dropdown-menu {
  position: absolute;
  z-index: 9999;
}

Why this feels impossible

You keep increasing z-index, but the dropdown never escapes. That happens because the menu is not competing against the whole page. It is competing inside the stacking context created by its parent.

If the next section belongs to a higher stacking context, the dropdown can still appear below it even with a massive number.

Error 4

The dropdown is inside a slider, table, or scroll wrapper

Some components clip overflow intentionally. Sliders hide offscreen slides. Responsive tables create horizontal scroll containers. Cards often use overflow:hidden for rounded corners. If your dropdown lives inside one of those wrappers, it may need a structural fix instead of a small CSS tweak.

Classic broken setup

Component clips
.table-scroll,
.slider-track,
.card {
  overflow: hidden;
}

.dropdown-menu {
  position: absolute;
  top: 100%;
}

The better fix

If the wrapper must keep overflow hidden, do not fight that wrapper forever. Move the dropdown outside the clipped element, render it in a higher layer container, or restructure the component so the menu is not trapped by the scrolling or clipping surface.

Advanced pattern

Render the dropdown in a higher layer when needed

In real apps, dropdowns, tooltips, popovers, and menus are often rendered into a dedicated layer near the end of the document. This keeps them away from clipped cards, scroll wrappers, and local stacking contexts.

Layer container idea

Portal style
<div class="app">
  <main>...page content...</main>

  <div class="ui-layer">
    <div class="dropdown-menu">...</div>
  </div>
</div>

Structural visual result

Dedicated UI layer
Dropdown / tooltip / popover layer

The menu is no longer trapped inside the clipped component that triggered it.

Fast practical rule

If your dropdown is getting cut off, do not start by adding bigger z-index numbers. First ask: is the menu clipped or layered behind something? If it is clipped, inspect parent overflow. If it is layered behind something, inspect stacking context. Those are different bugs.

Safer dropdown baseline

Production-minded
.nav {
  position: relative;
  z-index: 50;
  overflow: visible;
}

.nav-item {
  position: relative;
}

.dropdown-menu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  min-width: 220px;
  z-index: 60;
}

Why this pattern is safer

The outer navigation has a meaningful stacking level. The item creates a predictable positioning anchor. The dropdown has a clear placement and does not rely on a random massive number. Most importantly, the parent is not clipping the menu.

This does not solve every app architecture, but it gives you a clean baseline before moving to a portal-style or higher-layer solution.

Debug checklist

  • Inspect the dropdown parent chain for overflow:hidden, overflow:auto, and overflow:scroll.
  • Temporarily set suspicious parents to overflow:visible to see whether the menu stops getting cut off.
  • Check whether the dropdown is physically clipped or simply behind another element.
  • Verify the trigger wrapper has a predictable positioning context, usually position:relative.
  • Verify the dropdown has intentional placement, usually position:absolute, top:100%, and left:0.
  • Inspect parents for stacking-context creators like transform, filter, opacity, isolation, and will-change.
  • Check whether the menu is inside a carousel, table wrapper, slider, card, or scroll container that must clip overflow.
  • If the parent must clip overflow, move the dropdown outside that clipped surface instead of fighting the wrapper.
Best first move Use DevTools to toggle parent overflow rules before changing z-index.
Most common false fix Setting z-index:999999 while a parent is still physically clipping the menu.
Most overlooked cause The dropdown lives inside a component that intentionally hides overflow for design reasons.
Better mindset Dropdown bugs are usually structure bugs. Layering is only one part of the diagnosis.

Final takeaway

A dropdown getting cut off is rarely solved by a bigger z-index alone. The real fix starts by identifying whether the menu is clipped by overflow, trapped inside a stacking context, or rendered inside a component that cannot allow visual escape.

Fix clipping first, layering second, and structure third. Once you separate those three problems, dropdown menus become much easier to debug and much less mysterious.

Want more fixes like this?

Explore the full FrontFixer fixes library and keep debugging with practical guides built for real front-end layout problems.