visibility:hidden vs display:none vs opacity:0

While all 3 properties make an element’s box seem invisible, there are crucial differences between them:

Property Painted In layout Stacking context Pointer events Keyboard events
opacity: 0; No Yes New Yes Yes
visibility: hidden; No Yes Varies No No
display: none; No No Varies No No
  • The “Painted” column indicates if the browser will paint the element’s background (e.g. background-image), #text content, and so on.
    • An element cannot be painted without also participating in the page’s layout, of course.
    • This is No for all 3 properties and values, as the browser won’t need to paint the element’s box as it’s invisible.
  • The “In layout” column indicates if the browser will compute the layout and dimensions of the element – along with any of its descendants not excluded from layout.
    • This is only No for display: none;, as with opacity: 0; and visibility: hidden; the browser will still determine the size of the element so it can correctly layout other elements relative to the current element (e.g. if you have span.hidden { visibility: hidden; display: inline; }).
  • The “Stacking context” column indicates that any use of opacity (except opacity: 1.0;) will create a new stacking-context, which complicates use of the position property.
  • The “Pointer events” column indicates if the element will respond to user-interaction from a pointing device, such as a mouse, touch-screen, stylus, etc.
    • e.g. with visibility: hidden; then the :hover state won’t work, and clicking the same element won’t apply :focus or :active either.
    • Additionally, the DOM won’t raise any pointer events you’d handle in JavaScript (e.g. visibility: hidden; won’t raise mouseclick, touchstart, etc – note that the click event can still be raised by certain elements, like <button> if invoked by the user using a non-pointer input method, such as with keyboard or voice (accessible) navigation means.
      • You can use pointer-events: none; to block pointer events, but this won’t block keyboard and other non-pointer input and so should not be used to disable an element because the user can still use the keyboard to interact with it (especially <button>, <input />, <select>, and <textarea>).
  • The “Keyboard events” column indicates if the element can be interacted-with using keyboard navigation (and possibly other navigation means).
    • This includes smart-device (smartphones’ and tablets’) browsers’ “Prev/Next Field” buttons for navigating <form> elements (as this uses tabindex).
    • Unlike how pointer-events can be disabled in CSS using pointer-events: none;, there is no CSS property to disable keyboard interaction.

This table shows a more complete comparison between the main values of those 3 properties:

Property Painted In layout Stacking context Pointer events Keyboard events Animatable
Opacity
opacity: 0; No Yes New Yes Yes Yes
opacity: 0.1; Yes Yes New Yes Yes Yes
opacity: 0.9; Yes Yes New Yes Yes Yes
opacity: 1; Yes Yes Varies Yes Yes Yes
Visibility
visibility: hidden; No Yes Varies No No Yes, with caveats
visibility: visible; Yes Yes Varies Yes Yes Yes, with caveats
Display
display: none; No No Varies No No No
display: contents; Text and children only Text and children only Varies Yes Yes No
Other
pointer-events: none; N/A N/A N/A No Yes No
  • The “Animatable” column indicates if that property can be used with a CSS transition (transition:) or CSS animation (@keyframes).

    • Crucially, the display: property cannot be animated, which is why we can’t use a @keyframes timeline to completely hide an element after the animation is complete.
      • But curiously, we can animate the visibility: property despite being non-continuous, albeit with caveats.
  • Also, don’t get confused by the similarly-named backface-visibility and content-visibility properties.

    • backface-visibility is only applicable to 3D transform operations.
    • content-visibility is an optimization to speed-up page rendering during initial page-load, but requires CSS Containment first, which is out-of-scope for this QA.

Leave a Comment