Back

Styling Components Conditionally with Tailwind CSS

Styling Components Conditionally with Tailwind CSS

As Tailwind offers developers a quick and efficient way to apply styles to HTML markup, using it for conditional styling can significantly speed up development and maintain consistency across a project, especially for complex user interfaces that require different styles under various circumstances. By the end of this blog, you will better understand conditional styling and how to perform it using Tailwind CSS.

As static styling commonly fails to deliver a responsive and interactive user experience, we usually need to create user interfaces that can adapt to different scenarios, user interactions, and certain conditions. For example, responsive layouts and form validation feedback require presentation changes based on various situations and interactions. Without a way to dynamically adjust styles, developers struggle to create user-friendly and visually appealing applications across different devices and use cases.

This is where conditional styling comes into the picture. By enabling us to apply styles dynamically based on specific conditions, we can create interfaces that respond to different application states and contextual changes. We can change the appearance of an element in real time, offering a more intuitive and engaging user experience. Overall, it provides a more powerful and reliable way for building adaptable and interactive applications.

This article assumes the reader has working knowledge of:

Let’s get started!

What is conditional styling?

Conditional styling is a tactic in web development in which the styles of an element are dynamically adjusted based on certain conditions, states, or criteria. This tactic allows us to create interactive, user-friendly, and responsive layouts that adapt to different situations and user interactions, enhancing user experience, interface interactivity, and the website’s responsiveness.

Among the conditions and aspects on which you can base upon to do conditional styling are the following:

  • User interaction
  • Responsive design
  • Elements change of state
  • User authentication

Conditional styling can be done through using different individual technologies or their combination, with the ones below being the most common:

  • CSS
  • JavaScript
  • CSS frameworks like Tailwind CSS
  • JavaScript frameworks like React

If you have used any JavaScript framework, such as Astro or React, you may be familiar with the conditional styling below.

const renderArticleList = (articles) => {
  return articles.map((article, index) => (
    <div
      key={article.id}
      className={index % 2 === 0 ? "bg-emerald-600" : "bg-sky-600"}
    >
      <div>
        <img src={article.image} alt={article.title} />
      </div>
      <div>
        <h3>{article.title}</h3>
        <p>{article.description}</p>
      </div>
      <div>
        <Tag bg={article.status === isInProgress ? "orange" : "green"} />
      </div>
    </div>
 ));
};

The above code gives each article card a different background depending on whether its index is odd or even. A card will receive a background of bg-emerald-600 if its position in the list is even or bg-sky-600 if it is odd.

Similarly, the article’s tag has a background of orange if it is still in progress or green if it is not.

In this article, we will focus on how you can implement it using Tailwind CSS. The features in CSS that can perform conditional styling are the same as in Tailwind.

What is the :has() relational pseudo selector?

The :has() relational selector is a functional pseudo-class that represents an element if any of the selectors passed as an argument match at least one element. In short, It allows you to select a parent element based on the presence of specific child elements or previous siblings.

The selector is a recent addition to CSS with many powerful aspects that enable us to perform conditional styling in advanced ways. It allows you to select elements based on the below relationships:

Parent-Child relationship

The pseudo-class allows you to select a parent element based on the presence of a child/children elements.

HTML

<div>
  <div>
    <h2>Session replay you can self-host</h2>
    <p>All you need to observe, measure and iterate on your product.</p>
    <div>
      <p>
 All the tools to engage with users when they need you the most and to
 solve their issues on the go.
      </p>
      <ul>
        <li>Live session viewing</li>
        <li>Remote control</li>
        <li>Time Travel</li>
        <li>Video calling</li>
      </ul>
    </div>
  </div>
  <div>
    <h2>Co-browsing</h2>
    <p>
 Assist users while they’re browsing your site, with live session viewing
 and video calls. No meeting links, no downloads.
    </p>
  </div>
</div>

CSS

div > div:has(> ul) {
  background-color: #f0f0f0;
  padding: 20px;
  border-radius: 5px;
}

In the above code, we are telling :has() to select a <div> that is a direct child of another <div> and has a <ul> as a direct child.

Output

Screenshot 2024-07-22 at 12.36.49 AM

Taking this to the next level, you can use :has() to perform logical operations and, based on the results, apply specific styles to an element. Let’s take a look at the example below.

HTML

<div class="tag-container">
  <p>Core features</p>
  <ul>
    <li class="session">Session Replay</li>
    <li class="tools">Developer Tools</li>
    <li class="analytics">Product Analytics</li>
    <li class="browsing">C0-Browsing</li>
    <li class="flag">Feature flags</li>
    <li class="testing">Usability Testing</li>
    <!-- <li class-"invisible">invisible</li> -->
  </ul>
</div>

CSS

ul:has(.testing):has(.tools) :is(.testing, .tools) {
  background: blue;
}

ul:has(.browsing, .invisible) :is(.browsing, .invisible) {
  background: rebeccapurple;
}

/* Won't be applied since the invisible class doesn't exist in the ul*/
ul:has(.analytics):has(.invisible) is(.analytics, .invisbile) {
  background: red;
}

Breakdown of code:

  1. The first style rule in our example above first checks if the list of Core features contains both the items testing and tools; if it does, it gives the two items a blue background.
  2. The second checks if the list contains either the browsing and invisible items, in which case, the browsing item was found and the styles applied.
  3. Our third style rule checks if the list contains the analytics and the invisible items to apply the styles. In this case, the invisible item is not found, and thus, the styles do not take effect.

Output

Screenshot 2024-07-13 at 11.07.18 AM

Sibling-sibling relationship

The pseudo-class allows you to select a parent element based on the presence of sibling elements.

HTML

<section>
  <div class="enterprise">
    <p>ADVANCED FEATURES</p>
    <ul>
      <li>Data connectors</li>
      <li>Export Reports</li>
      <li>Session Vault</li>
    </ul>
  </div>
  <div class="enterprise">
    <p>OPS</p>
    <ul>
      <li>Scaling Capabilities</li>
      <li>Multitenancy</li>
      <li>Monitoring Stack</li>
      <li>Configurable backups</li>
    </ul>
  </div>
  <div class="support">
    <p>SUPPORT</p>
    <ul>
      <li>Developer Pairing</li>
      <li>Team Training</li>
      <li>Customer Success</li>
    </ul>
  </div>
  <div class="enterprise">
    <p>SECURITY</p>
    <ul>
      <li>SAML-Based SSO</li>
      <li>Project Permissions</li>
      <li>Role-Based Access Control</li>
      <li>Audit Trail</li>
    </ul>
  </div>
</section>

CSS

.enterprise:has(+ .support) li::before {
  content: '🚀';
}

Let’s break down the above CSS code:

  1. .enterprise: Selects any element with the class enterprise.
  2. :has(+ .support): Selects a <div> that is immediately followed by a sibling with the support class.
  3. li::before: Inserts content before each list item.

In short, we are selecting a <div> with an enterprise class, that is immediately followed by a sibling with a support class, changing its list icons to a rocket.

The expected output would look like something below.

Screenshot 2024-07-22 at 12.14.27 AM

Complex selectors

You can also use the :has() selector in combination with other relative selectors and pseudo-classes to create complex selectors that target elements in more advanced ways.

input:has(+ label.faulty):not(:focus):not(:placeholder-shown) {
  border: 1px solid #ff4444;
}

Breakdown of the code:

  1. input:has(+ .label.faulty): Selects an input that is followed by a sibling label element with the class faulty.
  2. :not(:focus): Selects inputs that are not focused
  3. :not(:placeholder-shown): Further targets inputs with no placeholders shown.

As such, in one go, our selector targets an input with a sibling label element with the class faulty, that is not focused and with no placeholder shown.

Additionally, you can also do responsive design using the selector. Given the dynamic nature of today’s websites, you can check the presence of an element using the selector, and if it exists, you can adjust the layout accordingly.

.gallery:has(.large-image) {
  grid-template-columns: repeat(2, 1fr);
}

For the above code scenario, you can consider a hypothetical case in which there is a gallery tool where a user uploads photos. However, whereas the gallery renders images well within an accepted size, the layout breaks when the user uploads images of larger sizes.

The moment our code detects that the gallery has a large image, it changes the layout to two columns from whatever the initial was to 1fr each.

You should note, however, that the :has() selector cannot be nested within another :has().

The selector was introduced in early March of 2022 and browsers slowly started to adopt it. As per CanIUse, all major browsers such as Chrome, Firefox, Brave, and Safari. It provides a CSS-native way to select elements based on their contents, reducing the need for JavaScript in many cases.

Screenshot 2024-07-11 at 9.11.42 PM

Source

For a better preview, I have summarized browsers’ support for the :has() relational selector.

Web support

BrowserDate
SafariMar 14, 2022
ChromeAug 30, 2022
EdgeSep 1, 2022
OperaSep 14, 2022
FirefoxDec 19, 2023

Mobile support

BrowserDate
Chrome (Android)May 15, 2024
Safari (iOs)Mar 14, 2022
Opera MobileMar 7, 2024
Firefox (Android)May 14, 2024
Android browserMay 15, 2024

We will see more real-world use cases for the :has() relational selector in the next section.

How to conditionally style elements in CSS?

Before we discuss how conditional styling can be implemented in Tailwind, let’s first examine how it can be done in CSS and the features we can use.

Apart from Media queries, Flexbox, and CSS Grid, the table below, the table below shows other CSS features that aid us in performing conditional styling.

Pseudo classesAttribute selectorsCSS Math functions
:not()a[title]calc()
:has()img[src="#"]min()
is()input[type="radio"]max()
:focusdiv[class="error"]clamp()

Since you have seen some examples of conditional styling in CSS in the :has() section, we will look at one example involving responsive design.

HTML

<div class="container">
  <div class="card">
    <div class="card-title">
      <!-- <svg></svg> -->
 Plugins
    </div>
    <div class="card-content">Card content...</div>
    <a
      href="#"
      class="learn-more">
 Learn more
    </a>
  </div>
  <!--   card 2    -->
  <!--   card 3     -->
  <!--   card 4     -->
</div>

CSS

@media only screen and (max-width: 600px) {
  .container {
    grid-template-columns: 1fr;
 }
}

In the above example, our four cards are initially in a two-column layout. However, we change the layout to a single column on the premise that the screen size on which they are displayed is no more greater than 600px.

Output

CSS responsive gif - 2-min

How to conditionally style components in Tailwind CSS?

We previously saw that the need to implement conditional styling usually comes about as a result of the three reasons below:

  1. User interactions
  2. Form validation feedback
  3. Need to improve responsiveness

As conditional responsive styling in Tailwind CSS isn’t much different from that in CSS, we’ll look at 1 and 2, and how you can do conditional styling in Tailwind CSS based on each.

User interactions

These include hovering, clicking, scrolling, or focusing an input. And when it comes to user interactions, you might want to style a child or parent element, based on an action done or happening on one of them. Tailwind provides ways to achieve this for both cases.

The code example below where the parent is styled in response to an interaction with the parent, provides a clearer picture.

HTML

<div class="group hover:scale-105 hover:cursor-pointer">
  <!-- Logo -->
  <div class="group-hover:blur-[4px]">
    <img alt="profile image" />
  </div>
  <!-- Message -->
  <div>
    <p class="group-hover:scale-105">
 “We went from spending hours debugging some production issues down to
 minutes after using OpenReplay.”
    </p>
  </div>
  <div>
    <!-- Profile -->
    <div class="group-hover:blur-[4px]">...</div>
  </div>
</div>

Breakdown of code:

  1. group: This class is put on the parent element. The class itself does not do any styling but marks the element as a group parent that child elements can reference.
  2. group-hover: This class comes from Tailwind’s group-* modifier which provides a set of modifiers we can apply to child elements of a group parent. You can replace the * with other state modifiers like focus and active. For our case, we are using hover.

In the above example, when a user hovers over the card, we scale it a bit in size, blur out its children, logo and profile, and put the focus on the testimonial text by increasing its font size.

The ouput of the above code would look like something below

gif modified 2-min

The condition can be anything from whether the parent has been visited to being disabled, and if the condition is met, the styles are applied.

Form Validation Feedback

Form validation is the process of checking user input in a web form to ensure it meets specific criteria before it’s submitted or processed. This ensures that the data entered is complete or correct. If otherwise, the user is given feedback that it isn’t.

The feedback given to the user when the data entered is not acceptable can be displaying error messages and highlighting form elements, thus guiding the user on how to correct the wrong data entered.

The example below shows you how to conditionally style the parent based on an action on the children.

<div
  class="has-[:user-invalid]:ring-2 has-[:user-invalid]:ring-red-500 has-[:user-invalid]:rounded-md">
  <div>
    <p class="text-center text-xl font-medium text-slate-800">
 Login to your account
    </p>
  </div>
  <form>
    <div>
      <label for="email">Email Address</label>
      <input
        type="email"
        name="email"
        id="email"
        placeholder="E.g johndoe@example.com"
        required />
    </div>
    <div>
      <label for="password">Password</label>
      <input
        type="password"
        name="password"
        id="password"
        placeholder="Password"
        required />
    </div>
    <div>
      <button type="submit">Submit</button>
    </div>
  </form>
</div>

The :has() pseudo selector comes to our rescue in our example above. We use Tailwind’s has-[]: modifier to select the parent element containing the entire form. Once the user interacts with the input and leaves without filling in the proper information, the parent element gets a red outline, indicating something went wrong with the filling.

The [] denotes Tailwind’s arbitrary value syntax, where you can put any valid custom CSS next to your Tailwind styles. The : denotes Tailwind’s style, which will be applied once the condition is met.

The output would look like something below

New gif-min

Alternatively, you can also use the arbitrary variants syntax straight away, and your code would now look something like this below. Both work well here.

<!-- Using the has-[] modifier -->
`has-[:user-invalid]:ring-2` 
`has-[:user-invalid]:ring-red-500` 
`has-[:user-invalid]:rounded-md`

<!-- Using arbitrary variants -->
`[&:has(:user-invalid)]:ring-2` 
`[&:has(:user-invalid)]:ring-red-500`
`[&:has(:user-invalid)]:rounded-md` 

The & coming right after the opening square bracket represents the current element, the parent div in our case.

There are probably better real-world use cases than this, but I hope you now have a clearer understanding of the concept from where we started.

Why use Tailwind CSS over CSS?

There are a couple of reasons why you might gravitate towards Tailwind CSS for conditional styling. Below are some of them.

Rapid development

With a utility-first approach, Tailwind CSS allows for faster development. You can style elements using the pre-defined classes in your markup instead of starting from scratch and switching between HTML and CSS files. This helps to speed up the overall project development as it saves time for tackling other tasks.

Optimized CSS output and enhanced performance

Through its purging feature, which removes unused styles, Tailwind CSS profoundly reduces CSS bloat, resulting in smaller file sizes than CSS approaches where unused styles often accumulate over time. The final lightweight CSS files produce a notable improvement in a website’s load time and performance.

Responsive design made easy

With the intuitive responsive design utilities Tailwind provides, such as sm and lg, you can quickly create adaptive layouts on different breakpoints by prefixing a breakpoint with the style you want to take effect, and it will take effect at the specified breakpoint.

<div class="bg-blue-600 text-white flex gap-4 lg:flex-col">
  <div>
    <img
      src="/..."
      alt="article image" />
  </div>
  <div>
    <h2 class="text-balance">
 Integrating OpenReplay with Slack in a Web Application
    </h2>
    <p class="text-lg sm:text-base">
 Send slack notifications from OpenReplay with this easy-to-follow
 integration!
    </p>
  </div>
</div>

Simplified Collaboration

Since Tailwind’s utilities are pre-defined and known, developers can easily understand and modify styles on new projects directly without much need to navigate complex CSS files searching for specific id’s or classes. This can be particularly valuable in larger teams where being on the same page is vital for all involved.

Customization and extensibility

You can seamlessly modify and extend Tailwind’s default configuration to match needs specific to your project. For example, you can use the arbitrary value syntax and arbitrary variants to write any custom CSS or you can extend its capabilities by using defining plugins or custom theme settings in your config.js .

This enables you to have the flexibility and agility to create a unique design while still leveraging Tailwind’s utility classes, thus making Tailwind quite customizable and extensible.

Community support

With Tailwind’s vibrant and active community, you are assured of finding contributions, bug fixes, and updates through articles, tutorials, social media posts, and GitHub issues on anything related to the technology. With more than 80k+ GitHub stars, 10,000 questions on Stack Overflow and large companies like Shopify, Twitch, and Stripe adopting the technology, you can easily be assured to find help regarding any Tailwind issue you may encounter.

Use cases of conditional styling in web development

After knowing what conditional styling is and how to perform, let’s now see some of the areas you can apply it.

User interaction

Elements can be styled differently based on some user interactions like a hover on the parent or entire component, to lead to some children elements having certain styles to enhance the interactivity and overall user experience. For example, when a user hovers over a testimonial card, you might want to scale it a bit, blur out some information, and increase the size of the testimonial text itself.

Form validation feedback

Providing feedback to a user when something goes wrong or is successful while interacting with a form, is one of the most common UI tasks on a website. This is usually visual and quickly communicates to the user, guiding them on what to do next for example applying a red border to an invalid input. This styling can be applied conditionally to the form element since it may be a fifty percent chance that any of the inputs will be invalid when the user interacts with them.

Data conditions - Change of state

A change of state or data condition can involve an element moving from empty to containing content, containing a specific attribute you want to target, an input being focused, or whether a button has been checked. You may want to conditionally style the element based on the state it is in or the data it contains. For example, for a div that may have an anchor with an empty href value, you may want to style it differently.

Responsive design

Perhaps this is one of the areas where conditional styling is done most. Adjusting layouts to based on different screen sizes and various devices to make the website or a specific component responsive, ensures accessibility to many of its users who use viewing gadgets of various screens. For example, due to limited space on mobile devices, one can decide to display an element vertically rather horizontally.

There is more use cases for conditional styling but the above are some of the commonest.

Conclusion

To wrap up, we have learned conditional styling and how to do it using the :has() pseudo-class. More so, we haveve looked at how to perform conditional styling in Tailwind CSS and areas in web development where to apply it. With the know-how you have gained, you can now implement conditional styling in your projects and create seamless and engaging UI components.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay