Skip to content

RESOURCES / BLOG

How can conditional visibility be used when elements need to “show” or “hide” in CSS?

CSS featured image

Toggling UI pieces is a daily task for frontend developers. Think dropdowns, modals, feature flags, skeleton loaders, responsive navigation, and progressive disclosure. The community often debates the best way to hide or show content while keeping UX smooth and accessible.

How can conditional visibility be used when elements need to “show” or “hide” in CSS?
I want to implement robust show and hide behavior for things like menus, accordions, and modals. I know about display, visibility, and opacity, but I am not sure when to use which approach, how to animate the transitions cleanly, and how to keep everything accessible. Are there CSS-only patterns I should know, and what are the gotchas for layout, focus, and performance?

Great question. There are several reliable patterns for conditional visibility, each with trade-offs. Below is a quick guide, followed by examples you can use in production.

  • display: none: Removes the element from layout and accessibility tree. Best for fully hiding elements. Not animatable directly.
  • visibility: hidden: Element takes space but is not painted. Remains in layout, not read by AT. You can transition to visibility: visible with opacity for nicer fades.
  • opacity: 0: Element is still in layout and focusable unless you also manage pointer-events: none and focus order. Good for fade animations, but make sure to trap focus correctly for modals.
<button id="toggle">Toggle panel</button>
<div id="panel" class="is-hidden">Content</div>

<style>
  .is-hidden { display: none; }
</style>

<script>
  const btn = document.getElementById('toggle');
  const panel = document.getElementById('panel');
  btn.addEventListener('click', () => panel.classList.toggle('is-hidden'));
</script>Code language: HTML, XML (xml)

Since height is often unknown, combine opacity with a large max-height for a smooth collapse.

<style>
  .collapse {
    overflow: hidden;
    max-height: 0;
    opacity: 0;
    transition: max-height 250ms ease, opacity 200ms ease;
  }
  .collapse.is-open {
    max-height: 1000px; /* large enough to fit content */
    opacity: 1;
  }
</style>

<div class="collapse" id="faq">Answer goes here...</div>Code language: HTML, XML (xml)
<input id="nav-toggle" type="checkbox" hidden>
<label for="nav-toggle">Menu</label>
<nav class="drawer">...</nav>

<style>
  .drawer { transform: translateX(-100%); transition: transform 200ms ease; }
  #nav-toggle:checked + label + .drawer { transform: translateX(0); }
</style>Code language: HTML, XML (xml)
<details>
  <summary>More options</summary>
  <div>Hidden content becomes visible when opened.</div>
</details>Code language: HTML, XML (xml)
<style>
  .desktop-only { display: none; }
  @media (min-width: 768px) {
    .desktop-only { display: block; }
  }
</style>

<div class="desktop-only">Visible on tablets and up</div>Code language: HTML, XML (xml)

For more layout and scaling tactics, see this guide on scaling elements with CSS.

  • When hiding content from assistive tech, pair visual hiding with aria-hidden="true" or move focus appropriately. When showing a modal, trap focus inside; when closing, return focus to the trigger.
  • Use prefers-reduced-motion to minimize animation for motion-sensitive users.
  • Prevent interaction with visually hidden elements using pointer-events: none or by removing them from the DOM via display: none.
  • Avoid layout shift by reserving space or using measured transitions. For alignment patterns, check centering and alignment techniques.

If you need to animate out then set display: none, use a two-step sequence: remove the open class to start the transition, listen for transitionend, then apply the hidden class.

// triggers fade-out

panel.classList.remove('is-open'); 

panel.addEventListener('transitionend', function onEnd(e) {
  if (e.propertyName !== 'opacity') return;
  panel.classList.add('is-hidden');
  panel.removeEventListener('transitionend', onEnd);
});Code language: JavaScript (javascript)

When content visibility depends on viewport or state, you can delay or conditionally load media. Use responsive URLs and lazy loading, and serve only what is needed. Cloudinary helps by producing fast, right-sized assets and by providing predictable image URLs you can swap as states change. For general performance strategy, see this optimization overview.

<img
  src="https://res.cloudinary.com/demo/image/upload/w_600,c_fill,q_auto,f_auto/sample.jpg"
  alt="Hero"
  loading="lazy"
  class="is-hidden">Code language: JavaScript (javascript)

Combine the earlier visibility classes with responsive images and conditional loading in your app logic.

  • Need complete hide, no layout space, not animating: Use display: none.
  • Need to animate opacity or scale: Keep in DOM, animate opacity or transform, then switch to display: none after transition.
  • Want CSS-only toggle: Use :checked or <details>.
  • Responsive visibility: Media or container queries that flip a class or direct CSS rule.

Choose display: none for complete removal, visibility or opacity for transitions, and pair with accessibility and focus management. For responsive cases, use media queries or semantic <details>. Optimize the visible media you deliver, and align layout to prevent jank.

Ready to optimize how your UI shows and hides content while delivering fast, responsive media? Create a free Cloudinary account and start streamlining your frontend workflow today.

Start Using Cloudinary

Sign up for our free plan and start creating stunning visual experiences in minutes.

Sign Up for Free