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.