Suggest an editImprove this articleRefine the answer for “Difference between visibility: hidden and display: none”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`display: none`** removes an element from the document flow: it takes no space and siblings reflow around it. **`visibility: hidden`** hides it visually but keeps the space, so nothing shifts. ```css .gone { display: none; } /* no space, triggers reflow */ .invisible { visibility: hidden; } /* space kept, repaint only */ ``` **Key rule:** need layout stability? Use `visibility: hidden`. Need full removal? Use `display: none`.Shown above the full answer for quick recall.Answer (EN)Image**`display: none`** removes an element from the layout flow entirely. **`visibility: hidden`** hides it but preserves its space in the document. ## Theory ### TL;DR - `display: none` is like erasing a chair from a room: other furniture shifts to fill the gap - `visibility: hidden` is like draping an invisible cloth over the chair: the space stays empty - Main difference: does the element hold its space after being hidden - Need stable layout while hiding? Use `visibility: hidden`. Removing completely? Use `display: none` - `visibility` can be animated with `opacity`; `display` cannot be transitioned at all ### Quick example ```html <!-- display: none - Box 3 moves up, gap is gone --> <div class="container"> <div class="box">Box 1</div> <div class="box" style="display: none;">Box 2</div> <div class="box">Box 3</div> </div> <!-- visibility: hidden - Box 3 stays in place --> <div class="container"> <div class="box">Box 1</div> <div class="box" style="visibility: hidden;">Box 2</div> <div class="box">Box 3</div> </div> ``` In the first container, Box 3 slides up to where Box 2 was. In the second, Box 3 stays exactly in place. ### Key difference `display: none` tells the browser to skip the element entirely during layout. No box model is generated: no width, no height, no position. Surrounding elements reflow as if it never existed. `visibility: hidden` still goes through the full layout pass, computes its dimensions, holds its space in the flow, but paints nothing. Toggling `display` triggers a full layout reflow. Toggling `visibility` only triggers a repaint, which is noticeably cheaper. ### When to use - **No layout shift needed** (tooltips, hover previews, aligned dropdowns): `visibility: hidden` - **Full removal from flow** (modals, conditional sections, dynamic list items): `display: none` - **Smooth fade animation**: combine `visibility: hidden` with `opacity: 0` - **Screen reader and tab order removal**: `display: none` paired with `aria-hidden="true"` ### Comparison table | Aspect | `display: none` | `visibility: hidden` | |---|---|---| | Space in layout | No (others reflow) | Yes (placeholder stays) | | Box model generated | No | Yes | | Animatable | No | Yes (with opacity) | | Screen reader | Skipped | Skipped | | Inherits to children | No | Yes (overridable) | | Triggers reflow | Yes | No (repaint only) | | When to use | Modals, collapse menus, conditional render | Tooltips, hover previews, dropdown alignment | ### How browsers handle this Chrome's Blink engine skips `display: none` elements entirely in the layout phase. They never enter the containing block formatting context, so siblings calculate positions as if those elements do not exist. `visibility: hidden` elements go through layout normally and get computed dimensions. The paint phase simply skips drawing them. That is the source of the reflow difference: one property affects layout, the other only affects paint. ### Common mistakes **Trying to animate display** ```css .menu { transition: opacity 0.3s; /* this works */ display: none; /* this does not animate */ } ``` `display` is a discrete property. The CSS Transitions spec does not allow transitioning it, so any transition on an element with `display: none` is ignored. The fix is `opacity` + `visibility`: ```css .menu { opacity: 1; visibility: visible; transition: opacity 0.3s, visibility 0.3s; } .menu.hidden { opacity: 0; visibility: hidden; } ``` **Forgetting visibility inheritance** ```css .parent { visibility: hidden; } /* all children hidden too */ .child { visibility: visible; } /* this DOES work - child becomes visible */ ``` Unlike `display: none`, visibility can be overridden on children. Setting `visibility: visible` on a specific child makes it show up even when the parent is hidden. This is useful when you need to reveal one element inside a hidden group. **Leaving focusable elements inside visibility: hidden** Both properties hide content from screen readers. But `visibility: hidden` does not remove elements from tab order. A hidden button inside a `visibility: hidden` container is still reachable by keyboard. `display: none` removes it from tab order completely. For form fields or buttons you want fully hidden, `display: none` is the safer choice. **Flexbox space not collapsing** ```css .flex-container { display: flex; } .flex-child { flex: 1; } .flex-child.hidden { visibility: hidden; } /* still takes its flex share */ ``` With `visibility: hidden`, the hidden child keeps its flex allocation and siblings do not expand. With `display: none`, siblings grow to fill the freed space. This trips developers working on equal-column layouts more often than expected. ### Real-world usage - React: `display: none` via conditional rendering (`{isOpen && <Modal />}`) for complete removal - Tailwind: `hidden` class = `display: none`; `invisible` class = `visibility: hidden` - Bootstrap: accordion collapse uses `display: none` to fully remove panel content - Vue: `v-show` sets `display: none`; `v-if` removes the element from the DOM entirely - Tooltip libraries: `visibility: hidden` + `opacity: 0` to avoid layout jumps on first show ### Follow-up questions **Q:** What triggers a reflow: toggling `display` or `visibility`? **A:** Toggling `display` triggers a full layout reflow. Toggling `visibility` only triggers a repaint. That is why `visibility` is cheaper for frequent show/hide cycles. **Q:** Can you animate from `display: none` to `display: block`? **A:** No. `display` is discrete and cannot be transitioned. Use `opacity` and `visibility` together instead. **Q:** Can a child override `visibility: hidden` set on a parent? **A:** Yes. Setting `visibility: visible` on a child makes it visible even if the parent is hidden. This does not work with `display: none`. **Q:** In a flex container, what happens to siblings when one item gets `visibility: hidden`? **A:** They keep their positions. The hidden item still holds its flex share, so siblings do not expand. With `display: none`, siblings would grow to fill the freed space. **Q:** Which is better for a tooltip that toggles frequently? **A:** `visibility: hidden` with `opacity`. It only triggers a repaint on toggle, not a full layout recalculation. The `visibility` + `opacity` combo is what I use in production tooltips by default - the transition stays smooth even at high frequency. ## Examples ### Layout difference in a flex row ```html <style> .row { display: flex; gap: 10px; margin-bottom: 20px; } .box { width: 100px; height: 100px; background: lightblue; display: flex; align-items: center; justify-content: center; } </style> <!-- display: none - the gap is gone --> <div class="row"> <div class="box">1</div> <div class="box" style="display: none;">2</div> <div class="box">3</div> </div> <!-- Renders: [1][3] --> <!-- visibility: hidden - the gap stays --> <div class="row"> <div class="box">1</div> <div class="box" style="visibility: hidden;">2</div> <div class="box">3</div> </div> <!-- Renders: [1][ ][3] --> ``` Box 3 sits in a different position in each row. That single visual difference determines which property to reach for. ### Smooth tooltip with visibility + opacity ```jsx function Tooltip({ children, text }) { const [visible, setVisible] = useState(false); return ( <div style={{ position: 'relative', display: 'inline-block' }}> <button onMouseEnter={() => setVisible(true)} onMouseLeave={() => setVisible(false)} > {children} </button> <div style={{ position: 'absolute', top: '110%', left: 0, padding: '4px 8px', background: '#222', color: '#fff', borderRadius: 4, whiteSpace: 'nowrap', visibility: visible ? 'visible' : 'hidden', opacity: visible ? 1 : 0, transition: 'opacity 0.2s, visibility 0.2s', }}> {text} </div> </div> ); } // visibility: hidden keeps dimensions computed - tooltip position stays stable on every show // display: none would recalculate position on each show, causing misalignment on first hover ``` The tooltip stays in the right position every time because its dimensions are always computed, just not painted. Switching to `display: none` here causes a visible flicker on first hover because the browser has to calculate the tooltip size from scratch.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.