What display: none Actually Does
display: none removes an element from the layout entirely. That means:
- It takes up zero space (as if it’s not in the HTML).
- It is not visible.
- It is not clickable and can’t be tabbed to.
- Its children are also removed from layout.
This makes it perfect for things that should be “not there” (like a closed dropdown panel), but it’s also why it’s not great for smooth transitions: there’s nothing to animate when something doesn’t exist in layout.
.demo .badge {
display: none;
}
.demo .badge {
display: inline-block;
}
.demo .badge {
display: block;
}
*,
::before,
::after {
box-sizing: border-box;
}
.demo {
font-family: ui-sans-serif, system-ui, sans-serif;
padding: 16px;
border: 2px solid #111;
border-radius: 12px;
max-width: 520px;
}
.badge {
padding: 6px 10px;
border: 2px solid #111;
border-radius: 999px;
background: #f2f2f2;
font-weight: 700;
}
.note {
margin-top: 12px;
padding: 10px 12px;
border: 1px dashed #111;
border-radius: 10px;
background: #fff;
}
I am a badgeSwitch snippets to see howdisplaychanges layout.
CSS display: none VS hidden (HTML Attribute)
The HTML hidden attribute is basically a built-in “hide this element” switch. In browsers, it behaves
like:
[hidden] { display: none; }
So hidden and display: none often produce the same visual result. The difference is mainly
how you control it:
hiddenis an HTML attribute. Great for toggling state in markup or JS.display: noneis CSS. Great for styling rules, responsive rules, and conditional display via selectors.
.card[hidden] {
display: none;
}
.card[hidden] {
display: block;
}
.card[hidden] {
display: block;
opacity: 0.35;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
font-family: ui-sans-serif, system-ui, sans-serif;
display: grid;
gap: 12px;
max-width: 560px;
}
.card {
border: 2px solid #111;
border-radius: 12px;
padding: 14px;
background: #f2f2f2;
}
.card strong {
display: inline-block;
margin-bottom: 6px;
}
.hint {
border: 1px dashed #111;
border-radius: 12px;
padding: 12px 14px;
}
Hidden card This element has thehiddenattribute.In browsers,hiddenbehaves likedisplay: none. In these snippets we override it to prove it’s just CSS under the hood.
CSS display: none vs visibility: hidden
These two hide things in very different ways:
display: none: removes the element from layout (no space).visibility: hidden: element is invisible, but it still keeps its space.
This is the reason your layout “jumps” when you use display: none, but it doesn’t when you use
visibility: hidden.
.item.is-hidden {
display: none;
}
.item.is-hidden {
visibility: hidden;
}
.item.is-hidden {
opacity: 0;
}
*,
::before,
::after {
box-sizing: border-box;
}
.row {
font-family: ui-sans-serif, system-ui, sans-serif;
display: flex;
gap: 10px;
flex-wrap: wrap;
padding: 14px;
border: 2px solid #111;
border-radius: 12px;
max-width: 560px;
}
.item {
width: 140px;
padding: 12px 10px;
border: 2px solid #111;
border-radius: 12px;
background: #f2f2f2;
text-align: center;
font-weight: 700;
}
ABCD
Extra: Clickability and Focus
Both display: none and visibility: hidden make the element non-interactive. But
opacity: 0 alone does not. That means you can accidentally create “invisible buttons” unless
you also disable pointer events.
Try hovering the button to the right of the Normal button, with snippet 1 active.
.ghost {
opacity: 0;
}
.ghost {
opacity: 0;
pointer-events: none;
}
*,
::before,
::after {
box-sizing: border-box;
}
.box {
font-family: ui-sans-serif, system-ui, sans-serif;
border: 2px solid #111;
border-radius: 12px;
padding: 14px;
max-width: 520px;
}
button {
padding: 10px 12px;
border: 2px solid #111;
border-radius: 10px;
background: #f2f2f2;
font-weight: 700;
cursor: pointer;
}
Learn more about visibility in the CSS Visibility Interactive Tutorial.
CSS display: none Transition
Here’s the big rule:
You cannot transition display.
Why? Because transitions animate between numeric/interpolatable values. display is not numeric and the
browser can’t animate “not in layout” into “in layout”. It’s either there or it isn’t.
The common solution: animate opacity and size instead
A practical pattern is:
- Use
opacityfor the fade. - Use something like
max-height(ortransform) for the “expand/collapse” feel. - Use
pointer-eventsto prevent interaction while hidden.
.wrap .panel {
max-height: 0px;
opacity: 0;
transform: translateY(-6px);
pointer-events: none;
overflow: hidden;
transition: max-height 350ms ease, opacity 250ms ease, transform 350ms ease;
}
.wrap:hover .panel {
max-height: 200px;
opacity: 1;
transform: translateY(0px);
pointer-events: auto;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
font-family: ui-sans-serif, system-ui, sans-serif;
border: 2px solid #111;
border-radius: 12px;
padding: 14px;
max-width: 560px;
}
.panel {
margin-top: 12px;
border: 2px solid #111;
border-radius: 12px;
padding: 12px;
background: #f2f2f2;
}
.panel p {
margin: 0;
}
.small {
margin-top: 8px;
font-size: 14px;
opacity: 0.85;
}
.hint {
margin-top: 10px;
font-size: 14px;
opacity: 0.85;
}
Panel titleThis panel animates on hover without using
display.We fake “collapse” with
max-height, and fade withopacity.Hover this box to open the panel.
A small note about max-height
If you want a “perfect” height animation without guessing a max-height,
you usually reach for JavaScript (measure content height and animate to that number),
or you use different layout tricks (See this Chrome Dev article on animating width and height to auto, and this Keith J Grant
article).
But for many, max-height is a solid starting point.
CSS display: none Animation
Just like transitions, display cannot be animated. But you can still create an
“enter/exit animation” by animating other properties and only using display: none at the very end.
CSS-only: animate a popover with hover (no display)
This is the simplest approach: keep the element in layout (or position it absolutely), then animate opacity + transform.
.popover {
transition: opacity 200ms ease, transform 200ms ease, visibility 0s linear 200ms;
}
.trigger:hover + .popover {
opacity: 1;
transform: translateY(0px);
visibility: visible;
pointer-events: auto;
transition: opacity 200ms ease, transform 200ms ease, visibility 0s linear 0s;
}
*,
::before,
::after {
box-sizing: border-box;
}
.demo {
font-family: ui-sans-serif, system-ui, sans-serif;
position: relative;
border: 2px solid #111;
border-radius: 12px;
padding: 16px;
max-width: 560px;
min-height: 150px;
}
.trigger {
display: inline-block;
padding: 10px 12px;
border: 2px solid #111;
border-radius: 10px;
background: #f2f2f2;
font-weight: 700;
cursor: default;
}
.popover {
position: absolute;
left: 16px;
top: 64px;
width: min(360px, calc(100% - 32px));
padding: 12px;
border: 2px solid #111;
border-radius: 12px;
background: #fff;
opacity: 0;
transform: translateY(-8px);
visibility: hidden;
pointer-events: none;
}
.popover p {
margin: 0;
}
Hover meI animate in/out with
opacityandtransform.
Notice the trick: visibility waits until the fade finishes (that 0s linear 200ms part), so
it doesn’t become “hidden” until after the opacity animation.
Learn more about CSS transitions in the CSS Transition Interactive Tutorial.
Using display: none only at the end
In real apps, a common approach is:
- Start hidden:
display: none. - When opening: set
displayto something (likeblock), then animate opacity/transform. - When closing: animate opacity/transform down to hidden, then set
display: none.
Step 3 requires either JavaScript (to wait for transition end) or a CSS-only approach that never truly uses
display: none during the animation. If you want pure CSS and smooth visuals, prefer the “no display”
approach shown above.
CSS display: none Not Working
When people say “display: none is not working”, it’s almost always one of these:
1) Specificity or order: your rule is getting overridden
If another rule later in the stylesheet (or a more specific selector) sets display back to something
else, your display: none won’t win.
.notice {
display: none;
}
.notice {
display: none;
}
.container .notice {
display: block;
}
.container .notice {
display: none;
}
*,
::before,
::after {
box-sizing: border-box;
}
.container {
font-family: ui-sans-serif, system-ui, sans-serif;
border: 2px solid #111;
border-radius: 12px;
padding: 14px;
max-width: 560px;
}
.notice {
margin-top: 10px;
padding: 12px;
border: 2px solid #111;
border-radius: 12px;
background: #f2f2f2;
}
Specificity demoI might be hidden… or not. Depends on the snippet.
If you’re debugging in DevTools, look at the “Computed” styles for display and you’ll see which rule is
winning.
Learn more about CSS specificity in the CSS Specificity Interactive Tutorial.
2) It is hidden, but you’re looking at the wrong element
Example: you hide a child, but the parent still shows an empty border/background, so it feels “not hidden”. Or you hide the parent but you’re inspecting the child.
3) Inline styles or !important
If an element has something like style="display: block", that inline style usually wins over your
stylesheet. And !important rules can also complicate things.
.box {
display: none;
}
.box {
display: none !important;
}
*,
::before,
::after {
box-sizing: border-box;
}
.box {
font-family: ui-sans-serif, system-ui, sans-serif;
border: 2px solid #111;
border-radius: 12px;
padding: 14px;
background: #f2f2f2;
max-width: 560px;
}
I have an inlinedisplay: block(Switch to the HTML tab to view). Snippet 1 may lose.
Rule of thumb: prefer fixing the selector/order rather than reaching for
!important, but it’s good to know it exists when you truly cannot change the source.
Learn more about !important in the CSS !important Interactive Tutorial.
CSS display: none Opposite
The “opposite” of display: none depends on what the element should be when visible:
- Text-like elements:
display: inlineorinline-block - Section containers:
display: block - Flex layouts:
display: flex - Grid layouts:
display: grid
If you don’t know what it should be, a safe choice for most containers is display: block, but not
always.
.box {
display: none;
}
.box {
display: block;
}
.box {
display: flex;
gap: 8px;
}
.box {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 8px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
font-family: ui-sans-serif, system-ui, sans-serif;
border: 2px solid #111;
border-radius: 12px;
padding: 14px;
max-width: 560px;
}
.box {
margin-top: 10px;
padding: 10px;
border: 2px solid #111;
border-radius: 12px;
background: #f2f2f2;
}
.pill {
padding: 8px 10px;
border: 2px solid #111;
border-radius: 999px;
background: #fff;
font-weight: 700;
text-align: center;
}
Show / layout modesOneTwoThree
Learn more about display: block; in the CSS Display: Block Interactive
Tutorial, about display: flex; in the CSS Flexbox Interactive Tutorial,
and about display: grid; in the CSS Grid Interactive Tutorial.
CSS display: none If Empty
Sometimes you want to hide an element only when it has no content (like an empty label, empty alert, or empty container).
Using :empty (works only if it is truly empty)
:empty matches elements that have no child elements and no text nodes. Important
gotcha: whitespace counts as text.
<div class="msg"></div>is empty.<div class="msg"> </div>is not empty (it has a whitespace text node).
.msg:empty {
display: block;
}
.msg:empty {
display: none;
}
*,
::before,
::after {
box-sizing: border-box;
}
.demo {
font-family: ui-sans-serif, system-ui, sans-serif;
border: 2px solid #111;
border-radius: 12px;
padding: 14px;
max-width: 560px;
display: grid;
gap: 10px;
}
.msg {
padding: 12px;
border: 2px solid #111;
border-radius: 12px;
background: #f2f2f2;
}
.label {
font-weight: 700;
}
This one is empty (should hide):This one has text (should show):Hello, I have content.
What CSS cannot detect
CSS can’t do “if this contains only whitespace” or “if this contains only an empty child somewhere deep” in a
reliable way across browsers. If your “empty” meaning is more complex than :empty, you typically solve
it with:
- Cleaning the HTML (don’t render the element if there’s no content), or
- JavaScript adding a class like
.is-empty.
CSS display: none Block
This phrase usually shows up in two situations:
- You want to hide something (
display: none), then show it as a block later (display: block). - You’re confused because
display: none“overrides” yourdisplay: blockelsewhere (specificity/order issue).
A clean toggle pattern with a class
Default visible, then hide when a class is present:
.toast {
display: block;
}
.toast.is-closed {
display: none;
}
.toast.is-closed {
display: none;
}
.toast {
display: block;
}
*,
::before,
::after {
box-sizing: border-box;
}
.toast {
font-family: ui-sans-serif, system-ui, sans-serif;
max-width: 520px;
border: 2px solid #111;
border-radius: 12px;
padding: 14px;
background: #f2f2f2;
}
.toast strong {
display: inline-block;
margin-bottom: 6px;
}
Saved! Your settings were updated.
If the toast should be flex or grid when visible, make that the default:
.toast { display: flex; }and.toast.is-closed { display: none; }
CSS display: none VS hidden (Quick Recap)
display: noneis a CSS rule you write in a stylesheet.hiddenis an HTML attribute that browsers treat likedisplay: noneby default.- Both remove the element from layout and interaction.
CSS display: none vs visibility: hidden
Summary
display: none: no layout space, no visibility, no interaction.visibility: hidden: keeps layout space, no visibility, no interaction.opacity: 0: keeps layout space, invisible, but still interactive unless you disable it.
Animation Patterns That Replace display: none
When you want something to “feel like” it’s appearing/disappearing, you typically pick one of these patterns:
1) Fade only: opacity
Good for: tooltips, subtle UI, overlays. Add pointer-events: none when hidden.
.fade {
opacity: 0;
pointer-events: none;
transition: opacity 250ms ease;
}
.wrap:hover .fade {
opacity: 1;
pointer-events: auto;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
font-family: ui-sans-serif, system-ui, sans-serif;
border: 2px solid #111;
border-radius: 12px;
padding: 14px;
max-width: 520px;
}
.fade {
margin-top: 12px;
padding: 12px;
border: 2px solid #111;
border-radius: 12px;
background: #f2f2f2;
}
.hint {
margin-top: 10px;
font-size: 14px;
opacity: 0.85;
}
Fade demoI fade in and out.Hover this box to fade the panel in.
2) Slide + fade: transform and opacity
Good for: dropdowns, popovers. Avoid animating layout when you can (transforms are usually smooth).
.dropdown {
opacity: 0;
transform: translateY(-10px);
pointer-events: none;
transition: opacity 200ms ease, transform 200ms ease;
}
.wrap:hover .dropdown {
opacity: 1;
transform: translateY(0px);
pointer-events: auto;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
font-family: ui-sans-serif, system-ui, sans-serif;
border: 2px solid #111;
border-radius: 12px;
padding: 14px;
max-width: 520px;
position: relative;
min-height: 160px;
}
.dropdown {
margin-top: 12px;
padding: 12px;
border: 2px solid #111;
border-radius: 12px;
background: #f2f2f2;
width: min(320px, 100%);
}
.hint {
margin-top: 10px;
font-size: 14px;
opacity: 0.85;
}
DropdownI slide and fade.Hover this box to open the dropdown.
3) Collapse: max-height + overflow: hidden
Good for: accordions with roughly known heights. Not perfect for unknown content size, but very common.
You already saw this pattern earlier, and it’s worth repeating because it shows up everywhere.
Accessibility Notes
display: noneremoves content from screen readers too. That’s usually correct for “closed” UI.- If something becomes visible, make sure keyboard users can reach it (and it’s not hidden via
opacitybut still focusable). - If you’re doing motion-heavy UI, respect users who prefer less animation with
@media (prefers-reduced-motion: reduce).
@media (prefers-reduced-motion: reduce) {
.panel {
transition: none;
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.panel {
font-family: ui-sans-serif, system-ui, sans-serif;
max-width: 520px;
border: 2px solid #111;
border-radius: 12px;
margin: 30px;
padding: 14px;
background: #f2f2f2;
transition: transform 250ms ease;
}
.panel:hover {
transform: translateY(-6px);
}
Hover me. If the user prefers reduced motion, the transition should be removed.
A Quick Debug Checklist for display: none
- Is the selector matching? (Check DevTools: does your rule appear?)
- Is another rule overriding it? (Specificity or later order.)
- Inline style? (Look for
style="display: ...".) - Are you hiding the right element? (Parent vs child confusion.)
- Are you confusing
displaywithopacity? (Opacity hides visually but can still intercept clicks.)
Final Takeaways
display: noneremoves an element from layout and interaction.visibility: hiddenhides it but keeps its space.- The HTML
hiddenattribute behaves likedisplay: noneby default. - You can’t transition or animate
display, but you can animateopacity,transform,max-height, and usevisibilitysmartly. - For “hide if empty”,
:emptyworks only when the element is truly empty (no whitespace).
