Skip to content
Carl Victor Fontanos
Carl Victor Fontanos

Carl Victor Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

:empty - Hide Containers That Have Nothing to Say

C
Carlo Fontanos
· 2 min read

Server-rendered templates leak empty elements: the errors div that's always in the markup, the badge span waiting for a count, the "related posts" section when there are none. Each renders as a ghost - a bordered nothing, a padded gap, a red dot with no number. CSS can see emptiness:

.badge:empty,
.alert:empty,
.tag-list:empty {
    display: none;
}

The badge is my favorite version: render <span class="badge"><?= $count ?: '' ?></span> unconditionally, and CSS hides it when the count is empty. The template loses its if-statement; presence logic becomes presentation logic, where this particular decision belongs.

The rule that trips everyone: whitespace counts

<div class="alert"></div>          <!-- :empty matches -->
<div class="alert"> </div>         <!-- does NOT match - a text node of one space -->
<div class="alert">
</div>                              <!-- does NOT match - newline + indentation -->
<div class="alert"><!-- note --></div>  <!-- matches - comments don't count -->

An element is :empty only with zero child nodes (comments excepted). Template engines love emitting whitespace inside "empty" tags, which is why this selector has a reputation for not working. Fixes: emit truly empty tags (<div class="alert"><?php ... ?></div> with no gaps), or trim in the template. There's no :blank selector shipping to save you - control your whitespace.

Empty-state messaging with ::before

.comment-list:empty::before {
    content: 'No comments yet - be the first!';
    display: block;
    padding: 2rem;
    text-align: center;
    color: #94a3b8;
}

An empty container announces itself. For AJAX-driven lists this is delightfully robust: JS clears the list, the message appears; JS appends rows, it vanishes - no state flags. (Screen-reader note: generated content is announced by modern AT, but for important empty states a real element toggled by :has() is more dependable.)

The grown-up sibling: :has() empty-state layouts

/* Style the whole panel differently when its list is empty */
.sidebar:has(.notification-list:empty) {
    display: none;
}

/* Or reserve the ghost only when content exists */
.results-panel:not(:has(.result)) { border: 0; padding: 0; }

:has() lets emptiness propagate upward - hide the section heading and wrapper when the list inside has nothing, which pure :empty (matching only the element itself) can't reach.

Support: :empty since the dawn of CSS3; the :has() combos since 2023. Small selector, but it moves a whole category of "should this render?" checks out of templates - and templates with fewer conditionals are templates with fewer bugs.

C
Written by Carlo Fontanos

Full-stack web developer sharing practical tutorials and building tools that ship.

Got something on your mind?

My inbox is open - no forms disappearing into the void here.

  • Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
  • Hire me for a project Have something custom in mind? Let's talk scope and timelines.
  • Product support Bought something here? I'll help you get it running.

I usually reply within 1-2 business days.

Message sent!

Your details are only used to reply to you.

Keep reading