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 Does My Overlay Not Cover the Whole Page?

Overlay not covering whole page bugs happen when an overlay is sized to a parent, placed under a header, trapped in a scroll area, or mounted in the wrong layer.

CSS Overlay Fix

Why Does My Overlay Not Cover the Whole Page?

An overlay not cover whole page bug usually means the overlay is not actually sized or layered against the full viewport. It may be positioned inside a parent, limited by a grid column, trapped under a sticky header, clipped by a scroll container, or using height:100% when the page needs viewport-based sizing.

This is one of those bugs that looks simple but can destroy the feel of a page. A backdrop that only covers half the screen makes the modal feel broken. A menu overlay that stops under the header feels unfinished. A loading overlay that covers only one section can confuse users about what is actually disabled.

  • overlay
  • fixed inset
  • viewport coverage
  • z-index layers

Test viewport ownership first

Temporarily change the overlay to position:fixed; inset:0; and move it near the end of the body or into a root overlay layer. If it suddenly covers the whole page, the original overlay was attached to the wrong parent or layer.

Related: Try this in the FrontFixer Live Inspector.

Open Live Inspector

What the bug looks like

The backdrop, loading screen, menu layer, or dimmed area covers only part of the page.

Why it happens

The overlay is filling a parent, section, grid column, or scroll container instead of the viewport.

What usually fixes it

Use a fixed root overlay with inset:0 and a clear z-index layer.

Why overlays fail to cover the full viewport

An overlay is supposed to communicate control. When a modal opens, a menu slides out, or a loading state appears, users expect the affected area to be obvious. If the overlay only covers the component, the main column, or the area below the header, the interface sends mixed signals.

The overlay not cover whole page problem usually comes from mixing local layout rules with global UI intent. A card overlay can be local. A full-page modal backdrop should not be local. A section loading shimmer can belong inside one section. A global loading lock should belong to the viewport.

The clean fix is to decide the overlay’s job first. If it should cover the whole page, mount it in a root layer and size it to the viewport. If it should cover only one card, keep it inside that card intentionally.

Local overlayGood for cards, sections, and small loading states.
Global overlayNeeded for modals, drawers, menus, and page locks.
Viewport sizingfixed plus inset:0 is the safe baseline.
Better mindsetCoverage is a layout decision, not decoration.
Error 1

The overlay uses height:100% inside a short parent

height:100% does not automatically mean the full page. It means the height of the containing block. If the parent is only a card, section, or wrapper, the overlay will only cover that parent.

Broken code

Parent-sized overlay
.section {
  position: relative;
}

.overlay {
  position: absolute;
  width: 100%;
  height: 100%;
}

Broken visual result

Only covers parent
Page header
Overlay only fills section
The overlay is doing exactly what the CSS says: it fills the parent, not the viewport.

Correct code

Viewport overlay
.overlay {
  position: fixed;
  inset: 0;
  width: auto;
  height: auto;
  z-index: 1000;
}

Fixed visual result

Covers viewport
Page still exists below
Overlay covers the whole viewport area
Use fixed inset when the overlay is supposed to cover the page.
Error 2

The header stays above the overlay

Sometimes the overlay covers most of the page but the sticky header remains visible. That usually means the header has a higher z-index or the overlay is mounted below a layer system that the header already owns.

Broken code

Overlay below header
.header {
  position: sticky;
  z-index: 100;
}

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

Broken visual result

Header leaks through
Overlay behind header layer
Sticky header still clickable
The overlay covers the viewport but loses the stacking battle to the header.

Correct code

Overlay above page chrome
:root {
  --z-header: 100;
  --z-overlay: 1000;
}

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

Fixed visual result

Overlay owns top layer
Header below overlay
Overlay is above header and page content
Give overlay layers a clear z-index token above normal page chrome.
Error 3

The overlay is trapped inside a scroll container

A scrollable parent can make an overlay cover only the visible panel or scroll with the content. This is common in dashboards, drawers, sidebars, tables, and app layouts with internal scrolling.

Broken code

Overlay inside scroll panel
.panel {
  max-height: 500px;
  overflow: auto;
  position: relative;
}

.panel .overlay {
  position: absolute;
  inset: 0;
}

Broken visual result

Panel-sized overlay
Overlay trapped in scroll panel
The overlay belongs to the scroll panel, so it cannot behave like a page overlay.

Correct code

Root overlay outside scroll
.panel {
  max-height: 500px;
  overflow: auto;
}

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

Fixed visual result

Root overlay ignores scroll
Overlay root covers full panel and page
The scroll panel can remain scrollable while the overlay sits outside it.
Error 4

The overlay is mounted inside the main grid column

In two-column layouts, the overlay may be placed inside the main content column instead of a page-level root. It then covers only the article area while the sidebar, header, or other page regions remain uncovered.

Broken code

Overlay inside main column
.layout {
  display: grid;
  grid-template-columns: 1fr 280px;
}

.main .overlay {
  position: absolute;
  inset: 0;
}

Broken visual result

Sidebar uncovered
Sidebar still exposed
Overlay only covers main column
The overlay is mounted in the main column, so the sidebar remains outside the covered area.

Correct code

Overlay outside layout grid
.layout {
  display: grid;
  grid-template-columns: 1fr 280px;
}

.page-overlay {
  position: fixed;
  inset: 0;
}

Fixed visual result

Whole layout covered
Sidebar below overlay
Overlay root covers the full page layout
Mount global overlays beside the layout grid, not inside one grid column.
Premium patterns

Three production-minded overlay coverage patterns

Premium overlay systems define coverage, scroll behavior, and layer order on purpose. Below are three different visual patterns so the overlay architecture does not become a repeated template.

Premium code example 1

Full-page overlay stack
:root {
  --z-header: 100;
  --z-backdrop: 900;
  --z-dialog: 1000;
}

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

.dialog {
  position: fixed;
  z-index: var(--z-dialog);
}

Premium visual result 1

Layer order blueprint
premium
Overlay stack above page chrome

The header stays in the app layer while backdrop and dialog own the overlay layer.

dialog backdrop header page
No exposed page zones
Pattern 1 is ideal for modal backdrops, confirmation dialogs, and blocking page states.

Premium code example 2

Mobile viewport overlay
.mobile-overlay {
  position: fixed;
  inset: 0;
  min-height: 100dvh;
  overflow: auto;
}

.mobile-overlay__panel {
  width: min(420px, 100%);
  min-height: 100dvh;
}

Premium visual result 2

Mobile coverage map
premium
Mobile overlay respects dynamic viewport

The overlay uses viewport units and internal scrolling instead of depending on page height.

100dvh overlay
fixed inset mobile viewport internal scroll safe coverage
Pattern 2 is ideal for mobile menus, drawers, checkout steps, and full-screen filters.

Premium code example 3

Local vs global overlay tokens
.card-overlay {
  position: absolute;
  inset: 0;
}

.page-overlay {
  position: fixed;
  inset: 0;
}

.is-page-locked {
  overflow: hidden;
}

Premium visual result 3

Coverage decision system
premium
Choose coverage before styling

The component can use a local overlay, while the app can use a global overlay root.

Card stateabsolute inset
Page statefixed inset
Locked statebody scroll off
Pattern 3 is ideal for design systems that need both section overlays and full-page overlays.

Fast practical rule

If the overlay should cover the whole page, use position:fixed, inset:0, and a root overlay layer. Do not rely on height:100%, main-column placement, or a local parent unless the overlay is intentionally local.

Debug checklist

  • Check whether the overlay is absolute or fixed.
  • Replace width and height rules with inset:0 for full-page overlays.
  • Inspect whether the overlay is mounted inside a card, section, grid column, or scroll container.
  • Compare the overlay z-index against sticky headers, menus, drawers, and modals.
  • Look for parent overflow:hidden or overflow:auto.
  • Use a root overlay layer for modals, page locks, loading screens, and menus.
  • Use local overlays only for local card or section states.
  • Test mobile viewport height with address bars and long content.
Best first moveTry position:fixed; inset:0 and retest coverage.
Most common causeThe overlay is filling a parent instead of the viewport.
Most sneaky causeThe header has a higher z-index than the overlay.
Better mindsetChoose local or global coverage before styling the overlay.

When a partial overlay is correct

Partial overlays are not always wrong. A card loading state, image hover layer, table blocker, or local section skeleton can correctly cover only one component. The problem begins when the overlay is meant to block the page but is mounted like a local component.

Use local overlays for local states. Use global overlays for global states. That simple separation prevents most coverage bugs before they appear.

This also keeps the article intent clean: this fix is about coverage area, not a generic modal bug or a generic z-index bug. The main question is whether the overlay owns the viewport or only owns a component.

The authority move is not to make every overlay full-screen. The authority move is to make the coverage match the user’s expectation.

Why this bug feels so visible

Users do not need to understand CSS to feel when an overlay is wrong. They see uncovered areas, clickable headers, floating sidebars, or page content that should be disabled but still looks active. That visual mismatch creates distrust quickly.

A correct overlay makes the interface state obvious. It tells users whether the whole page is blocked, one section is loading, or a focused dialog needs attention.

Final takeaway

An overlay not cover whole page bug usually happens because the overlay is local while the design expects it to be global. The CSS may be filling a parent, grid column, scroll panel, or lower z-index layer instead of the viewport.

Use a root overlay layer, position:fixed, inset:0, and clear z-index tokens when the whole page must be covered. Keep partial overlays only when the component itself is the intended coverage area.

Want more fixes like this?

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

Why Does My Modal Stay Trapped Inside a Parent?

Modal trapped inside parent bugs happen when a dialog is mounted inside a clipped, transformed, scrolling, or locally positioned component.

CSS Overlay Fix

Why Does My Modal Stay Trapped Inside a Parent?

A modal trapped inside parent usually means the dialog is not living in a true page-level overlay layer. The modal may be nested inside a card, section, scroll container, tab panel, transformed wrapper, or component with overflow:hidden. When that happens, the modal can get clipped, appear too small, sit behind nearby UI, or fail to cover the page.

This is not the same as a normal modal z-index mistake. A bigger z-index may not fix it because the modal is still inside the wrong parent. The parent controls the visual boundary, stacking context, scroll area, or positioning context. The modal is trying to act global while the HTML structure says it is local.

  • modal
  • parent boundary
  • overflow hidden
  • overlay root

Test the parent boundary first

Temporarily move the modal markup outside the component in DevTools, or remove parent rules like overflow:hidden, transform, and position:relative. If the modal suddenly covers the page correctly, the parent was the trap.

Related: Try this in the FrontFixer Live Inspector.

Open Live Inspector

What the bug looks like

The modal opens, but it stays trapped inside a card, panel, section, sidebar, or scrollable component.

Why it happens

The modal is mounted inside a parent that controls clipping, positioning, scrolling, or stacking.

What usually fixes it

Move the modal to a root overlay layer and keep only the trigger inside the component.

Why a modal can be trapped by its own component

A modal looks like it should belong to the whole page, but the browser does not guess that intention. The browser follows the DOM and CSS. If the modal element is placed inside a product card, tab panel, sidebar widget, carousel slide, or article section, it still has to deal with that parent’s layout rules.

A modal trapped inside parent often starts as a convenience decision. The developer places the modal markup right beside the button that opens it. That feels organized, but it can make a global overlay depend on a local component. If the component has clipping, transform, scroll, or a local stacking context, the modal inherits that problem.

The cleaner architecture is to keep the trigger local and the dialog global. The button can live inside the card. The modal root should usually live outside the card, near the end of the document or inside a dedicated overlay root.

Local trigger is fineThe button belongs inside the component.
Global dialog needs rootThe overlay should not depend on the card boundary.
Parent rules matterOverflow, transform, position, and scroll can all trap it.
Better mindsetKeep component UI local and page overlays global.
Error 1

The modal is inside a card with overflow hidden

Cards often use overflow:hidden to clip rounded corners, images, badges, or hover effects. If the modal is nested inside that card, the card can clip the modal even when the modal has a huge z-index.

Broken code

Modal inside clipped card
.product-card {
  position: relative;
  overflow: hidden;
}

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

Broken visual result

Card clips modal
Modal stuck inside card
The modal is clipped by the same card that clips its image corners.
The modal is high, but the parent boundary is stronger than the child’s intent.

Correct code

Modal root outside card
.product-card {
  overflow: hidden;
}

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

Fixed visual result

Root modal covers page
Card stays clipped
Modal root owns the viewport
Leave card clipping on the card, but mount the modal outside the card.
Error 2

The modal is absolute inside a small section

A modal that uses position:absolute may use the nearest positioned parent as its reference. If that parent is a small section or tab panel, the modal covers only that local area instead of the full page.

Broken code

Absolute modal in section
.settings-panel {
  position: relative;
}

.settings-panel .modal {
  position: absolute;
  inset: 0;
}

Broken visual result

Only covers local panel
Settings panel parent
Page still visible
Modal only fills section
The modal is positioned against the section, not the viewport.

Correct code

Fixed modal in root
.settings-panel {
  position: relative;
}

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

Fixed visual result

Covers the viewport
Settings panel stays local
Page content
Fixed modal covers the whole browser area
Use a root fixed modal when the dialog should block the full page.
Error 3

The modal is inside a scroll container

Scroll containers are common in dashboards, sidebars, and app panels. If the modal lives inside one of those scroll areas, the overlay can scroll with the content or be limited to that container’s height.

Broken code

Modal inside scroll area
.panel-body {
  max-height: 420px;
  overflow: auto;
}

.panel-body .modal {
  position: absolute;
}

Broken visual result

Scroll area traps overlay
Modal scrolls inside panel
The dialog is limited by the same scroll container as the panel content.

Correct code

Panel scroll separated
.panel-body {
  max-height: 420px;
  overflow: auto;
}

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

Fixed visual result

Modal ignores panel scroll
Root modal sits above panel
Panel content can scroll, but the page-level modal should not be trapped inside it.
Error 4

The modal is owned by a reusable component

Reusable components often keep their markup self-contained. That can be convenient, but a modal that must cover the whole interface should not always be owned by the same component that opens it.

Broken code

Dialog inside component
<Card>
  <button>Open modal</button>
  <Modal />
</Card>

Broken visual result

Component owns dialog
Card component
Trigger Local state
Modal trapped
Neighbor UI
Still wins Local layers
The modal is packaged inside the component, so it inherits component boundaries.

Correct code

Trigger local, modal global
<Card onOpen={openModal} />

<ModalRoot>
  <Modal />
</ModalRoot>

Fixed visual result

Modal root owns dialog
Card component
Trigger only No overlay trap
Modal root
Backdrop Dialog
Global modal
The component opens the modal, but the root layer owns the modal’s layout.
Premium patterns

Two production-minded modal architecture patterns

Premium modal architecture separates trigger ownership from overlay ownership. Below are two different premium patterns: a layer-cake overlay map and a portal routing system.

Premium code example 1

Root overlay layer
<main class="app">
  <ProductCard />
</main>

<div id="modal-root"></div>

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

.modal-dialog {
  position: fixed;
  z-index: var(--z-modal);
}

Premium visual result 1

Overlay layer cake
premium
Modal layer system

The app content stays below while backdrop and dialog live in the root overlay stack.

dialog layer backdrop layer component triggers base page
Root owns modal depth
Pattern 1 is ideal for product previews, account dialogs, checkout modals, and confirmation overlays.

Premium code example 2

Portal route from component to root
.card {
  overflow: hidden;
}

.card__button {
  position: relative;
}

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

Premium visual result 2

Portal routing system
premium
Component trigger, root dialog

The card keeps its clipped design. The portal sends the modal to a safe viewport layer.

Product card clipped image local button
Modal root backdrop dialog
Pattern 2 is ideal for component libraries, React portals, checkout drawers, and reusable card modals.

Premium code example 3

Mobile-safe dialog shell
.modal-root {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
}

.modal-dialog {
  width: min(520px, calc(100% - 32px));
  max-height: calc(100dvh - 32px);
  overflow: auto;
}

Premium visual result 3

Mobile-safe modal shell
premium
Viewport-safe dialog

The modal belongs to the root, but the dialog itself stays readable and scrollable on small screens.

dialog fits viewport
root fixed backdrop scrollable dialog
Pattern 3 is ideal when the modal can contain long forms, checkout steps, legal text, or settings panels.

Fast practical rule

If a modal is trapped inside a parent, stop increasing z-index and check where the modal is mounted. A page-level modal should usually be mounted in a page-level overlay root, not inside the card, section, or scroll container that opened it.

Debug checklist

  • Find the modal element in the DOM and identify its parent chain.
  • Check whether any parent has overflow:hidden or overflow:auto.
  • Check whether the modal uses position:absolute instead of viewport-level fixed positioning.
  • Look for local parents with position:relative.
  • Inspect transformed, filtered, or opacity-based parents that create local contexts.
  • Move the modal to a root overlay layer and keep the trigger inside the component.
  • Use separate z-index tokens for backdrop, dialog, header, drawer, and toast layers.
  • Test long modal content on mobile so the dialog scrolls internally instead of escaping the viewport.
Best first moveMove the modal markup outside the component and retest.
Most common causeThe modal is nested inside a card or panel with clipping.
Most sneaky causeThe trigger component owns the modal because it felt convenient.
Better mindsetTrigger local, dialog global.

When a local dialog is still okay

Not every pop-up needs to be a full page-level modal. A small tooltip, dropdown, inline editor, or component-only popover may correctly stay inside its parent. The key question is whether the UI is supposed to cover the page or only belong to the component.

If it must block the whole page, dim the background, or capture the user’s focus across the interface, treat it as a root overlay. If it only edits one card or shows one small hint, a local component layer may be fine.

The authority move is to choose the ownership level intentionally instead of letting markup convenience decide the modal architecture.

Why this bug hurts trust fast

A broken modal is not a subtle detail. Users immediately notice when a dialog opens inside the wrong box, gets cut off, hides behind the page, or refuses to cover the content. It makes the interface feel unfinished even if the rest of the layout is polished.

That is why modal architecture is a production concern, not just a visual tweak. The modal must be mounted where its job makes sense. A global modal needs a global layer.

Final takeaway

A modal trapped inside parent is usually an architecture problem, not just a z-index problem. The modal is mounted inside a parent that controls clipping, positioning, scrolling, or stacking.

Keep the trigger where it belongs, but move the modal to a root overlay layer when it needs to behave like a full-page dialog. That keeps cards, sections, and panels clean while the modal does its real job.

Want more fixes like this?

Browse more CSS overlay, modal, z-index, stacking context, dropdown, and responsive layout 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 Button Not Clickable?

Button not clickable bugs usually happen when the visible button and the real clickable layer are not the same thing. An invisible overlay, disabled state, pointer-events rule, or broken HTML structure can make a button look normal while clicks go nowhere.

Interaction Fix

Why is my button not clickable?

A button can look perfectly fine and still refuse to click. The color is right, the hover state may appear, the spacing looks clean, and the design seems finished. But the browser does not click what your eyes see. It clicks the topmost interactive layer under the pointer. If another element is covering the button, if pointer-events is wrong, if the button is disabled, or if the markup is not truly interactive, the UI can feel dead even though the visual design looks normal.

  • Invisible overlays
  • Pointer-events bugs
  • Disabled states
  • Broken hit areas

What the bug looks like

The button is visible, styled correctly, and seems ready to work, but clicks do nothing, only part of the button responds, or the button works in one layout but not another.

Why it happens

The browser usually is not ignoring the button. Something in the stacking order, pointer behavior, disabled state, markup, or hit area is blocking the click.

What usually fixes it

Use DevTools to inspect the topmost layer under the cursor, then check pointer-events, disabled, semantic markup, and the real size of the clickable target.

Why a button can look clickable but still be dead

A button bug is often not a button-design bug. It is an interaction-layer bug. The visible button may be behind another layer, inside a disabled form state, covered by a pseudo-element, or visually larger than the actual clickable element.

This is why blindly changing colors, padding, or hover styles rarely fixes the problem. You need to find what element is actually receiving the click. The same idea appears in other FrontFixer layout bugs: with z-index problems, what appears visually on top may not be in the layer system you think; with dropdowns getting cut off, the issue may be a parent wrapper rather than the dropdown itself.

Error 1

An invisible overlay is stealing the click

This is the most common reason a button is not clickable even though it looks normal. A decorative layer, pseudo-element, full-card overlay, modal backdrop, or animation layer is placed above the button. The user thinks they are clicking the button, but the browser is clicking the invisible layer instead.

Broken code

Overlay wins
.card {
  position: relative;
}

.card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
}

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

Broken visual result

Click blocked
Save changes

The button is visible, but an invisible layer is sitting above it and receives the click first.

Correct code

Clicks pass through
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

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

Fixed visual result

Click reaches button
Save changes

The decorative layer no longer steals pointer events, and the real button is above it in the stacking order.

Error 2

pointer-events:none is on the wrong element

pointer-events:none can be useful on decorative layers, but it is dangerous on real interactive elements. If the button itself, or a parent wrapper, has pointer events disabled, the UI may render normally while ignoring clicks.

Broken code

Dead interaction
.button {
  pointer-events: none;
}

Broken visual result

Looks active, ignores click
Checkout button pointer-events: none
Newsletter button parent blocks it
Card CTA no click target

The button can still look styled, but the browser is told not to treat it as a pointer target.

Correct code

Interactive target
.button {
  pointer-events: auto;
}

.decorative-overlay {
  pointer-events: none;
}

Fixed visual result

Real button receives pointer
Checkout button clickable
Newsletter button clickable
Card CTA clickable

Pointer events should be disabled on decorative layers, not on the button users need to click.

Error 3

The button is disabled but still looks active

A disabled button is supposed to ignore clicks. The bug happens when the visual design does not make that disabled state obvious. Developers then spend time debugging JavaScript or CSS when the markup already says the button cannot be clicked.

Broken expectation

Markup says disabled
<button class="button" disabled>
  Save changes
</button>

Broken visual result

Disabled state

Account settings

The button may look designed, but the HTML state blocks interaction.

disabled

If the button has disabled, it cannot be clicked until that state is removed.

Correct state

Active button
<button type="button" class="button">
  Save changes
</button>

Fixed visual result

Enabled state

Account settings

The button is now semantically enabled and can receive clicks.

enabled

Good UI makes disabled and enabled states visually clear, so users and developers do not confuse them.

Error 4

The visual button is larger than the real clickable target

Sometimes the full visual shape looks like a button, but only a small text link inside it is actually clickable. This creates the frustrating “only part of my button works” bug. The visual hit area and the real interactive element must match.

Broken structure

Tiny real target
<div class="button-look">
  <a href="/checkout">Checkout</a>
</div>

Broken visual result

Only a small area works
Big visual button
The visual button is large, but the actual clickable anchor is much smaller.

Users click the large visual area, but only the small nested link actually receives navigation.

Correct structure

Full target
<a class="button" href="/checkout">
  Checkout
</a>

Fixed visual result

The full button is clickable
Full clickable button
The link itself owns the full visual shape, so the hit area matches what users see.

The clickable element should usually be the same element that creates the visual button shape.

Error 5

The HTML structure is invalid or fighting the browser

Button bugs can also come from invalid structure: buttons inside links, links inside buttons, clickable wrappers inside clickable wrappers, or custom components that use a <div> where a real <button> should be used.

Fragile markup

Nested interaction
<a href="/pricing">
  <button>View pricing</button>
</a>

Why this is risky

Nesting interactive elements makes click behavior harder to predict and can create accessibility problems. The browser, screen readers, and keyboard navigation may not treat the UI the way you expect.

Cleaner markup

One interactive element
<a class="button" href="/pricing">
  View pricing
</a>

<button type="button" class="button">
  Open modal
</button>

Better rule

Use a link when the action navigates somewhere. Use a button when the action changes something on the current page. Do not nest one interactive element inside another.

Fast practical rule

If your button is not clickable, do not start by rewriting the button style. First use DevTools to inspect what element is actually under the cursor. If the selected element is not the button, you have a layer or hit-area problem. If it is the button, check disabled, pointer-events, event listeners, and semantic markup.

How to debug the click target in DevTools

Open DevTools and use the element picker. Move the cursor over the button and watch which element gets highlighted. If an overlay, pseudo-element, wrapper, or backdrop is selected instead of the button, the browser is telling you exactly why the click does not reach the button.

Then temporarily disable suspicious CSS rules: z-index, position:absolute, inset:0, pointer-events, opacity, and overlay pseudo-elements. The goal is not to guess. The goal is to reveal the real click layer.

Quick temporary debug CSS

Find blockers
* {
  outline: 1px solid rgba(255, 106, 61, .35);
}

.card::before,
.overlay,
.backdrop {
  outline: 3px solid red;
}

Safe CTA pattern

Navigation button
<a class="button" href="/fixes/">
  Browse fixes
</a>
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 18px;
  border-radius: 999px;
  background: #ff6a3d;
  color: #fff;
  position: relative;
  z-index: 2;
}

Why this pattern is safer

The link owns the full button shape. The hit area matches the visual shape. The element is semantically correct for navigation, and the position plus z-index gives it a predictable place if decorative layers exist around it.

For actions that open a modal, submit a form, or change the current interface, use a real <button> instead.

Debug checklist

  • Use DevTools element picker and confirm the button is the element actually under the cursor.
  • Check for overlays, pseudo-elements, full-card links, modal backdrops, sticky bars, or wrappers covering the button.
  • Inspect ::before and ::after on parent containers.
  • Look for pointer-events:none on the button or any ancestor.
  • Check whether the button has the disabled attribute.
  • Confirm the visual hit area and the real clickable element are the same size.
  • Avoid nesting buttons inside links or links inside buttons.
  • Use <a> for navigation and <button> for in-page actions.
  • Test mobile separately, because overlays and menu layers often change across breakpoints.
  • Do not assume the CSS class is broken until you know what layer receives the click.
Best first move Inspect the exact element under the cursor before editing the button styles.
Most common false fix Raising the button z-index without checking whether the overlay should use pointer-events:none.
Most overlooked cause A pseudo-element covers the whole card and silently steals every click.
Better mindset A button not clickable bug is usually about hit testing, not just styling.

Final takeaway

When a button is not clickable, the visible design is not enough evidence. The browser clicks the real topmost interactive layer, not the layer you intended users to click. That means invisible overlays, pseudo-elements, disabled states, pointer-event rules, and invalid markup can all make a normal-looking button feel broken.

Start by identifying the actual click target in DevTools. Then remove blockers, restore pointer events, fix disabled states, and make sure the visual button and the real interactive element are the same thing. Once you debug the interaction layer, button bugs become much easier to fix.

Want more fixes like this?

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

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.