Show or hide an element by parent class in Tailwind

By

Published · Updated

Tagged: CSS

Learn a lesser-known Tailwind trick that shows or hides an element based on a parent class, using the group utility with the group-[.is-published] variant.

Little relatively unknown Tailwind trick using group:

<div class="group is-published">
  <div class="hidden group-[.is-published]:block">
    Show only when parent has class "is-published"
  </div>
</div>

The inner div is hidden by default. It shows up only when the parent has the is-published class.

How it works

group is a marker class. It adds no styles, it only tells Tailwind “this is the parent I want to look at”.

group-[...] takes any CSS selector between the square brackets. The utility after the colon applies when the element with the group class matches that selector.

This is the CSS Tailwind generates:

.group-\[\.is-published\]\:block:is(:where(.group):is(.is-published) *) {
  display: block;
}

You can read 2 things from it.

is-published must be on the same element that has group. If you put group on the parent and is-published on the grandparent, nothing happens.

The element can be at any depth inside the group. It does not have to be a direct child.

Toggle the class with JavaScript

Here is an example you can paste in a page that uses Tailwind:

<article id="post" class="group">
  <span class="hidden group-[.is-published]:inline">Published</span>
  <span class="group-[.is-published]:hidden">Draft</span>
  <button id="toggle">Toggle</button>
</article>

<script>
  document.querySelector('#toggle').addEventListener('click', () => {
    document.querySelector('#post').classList.toggle('is-published')
  })
</script>

The first span is the trick from before.

The second one does the opposite. It’s visible by default, and group-[.is-published]:hidden hides it when the parent gets the class.

You can also write the “parent does not have the class” case with :not():

<span class="hidden group-[:not(.is-published)]:inline">Draft</span>

Skip the group class with in-*

Tailwind 4 added the in-* variant. It works like group, but you don’t add any class to the parent:

<div class="is-published">
  <div class="hidden in-[.is-published]:block">
    Show only when a parent has class "is-published"
  </div>
</div>

The difference is that in-* looks at every ancestor, at any level. If any element above this one has is-published, the div is shown.

That’s fine most of the time. When you want to react to one specific parent, use group.

Nested groups

If you nest elements with group, an inner element reacts to all of them. In this example the badge shows up, because one of the groups above it has the class:

<article class="group is-published">
  <div class="group">
    <span class="hidden group-[.is-published]:inline">Approved</span>
  </div>
</article>

To look at one group only, give each group a name with group/name, and add the same name after the brackets:

<article class="group/post is-published">
  <div class="group/comment">
    <span class="hidden group-[.is-published]/comment:inline">Approved</span>
  </div>
</article>

Now the badge stays hidden. It only looks at group/comment, and that element does not have the class.

You can pick any name. There is nothing to configure.

Use a data attribute instead of a class

If the state lives in a data-* attribute, Tailwind has a variant for that too:

<div class="group" data-state="published">
  <div class="hidden group-data-[state=published]:block">Published</div>
</div>

In Tailwind 4 you can also check that an attribute is there, without a value:

<div class="group" data-published>
  <div class="hidden group-data-published:block">Published</div>
</div>

If it does not work

Check these things:

Want me to talk about your product? You can sponsor this site.

Related posts about css: