Why you don't need every CSS pseudo-selector in Tailwind CSS
Tags: tailwindcss, css, react, javascript
Published: 2021-04-05 09:37:27

Tailwind is great. And it is often misunderstood. If you want to know why Tailwind doesn't have a lot to do with CSS inline styles, read my [previous post](https://dev.to/wheelmaker24/why-tailwinds-utility-first-approach-supersedes-pseudo-selectors-154f).

But today it's about something else: People often get confused when it comes to CSS pseudo-selectors in Tailwind.

Tailwind supports the most important pseudo-selectors out of the box, e.g. `:hover`, `:focus` and `:active`. You may use these selectors as "variants" in front of your utility-class like this:

```html
<button class="bg-black hover:bg-gray-500 text-white">
  my button
</button>
```

As expected, this example would add a gray background on hover to your button element.

It makes sense to use Tailwind variants for different states (e.g. `active`, `hover`, `visited`, `focus`, …), as rebuilding these states via JavaScript will not give you the same performance. But when it comes to other pseudo selectors than these, it is at least worth discussing:

## `::before` and `::after` elements in Tailwind

`::before` and `::after` elements are not supported by Tailwind out of the box. While you can add them via plugins, I would not recommend doing so.

Let's think about what these CSS selectors do: They render pseudo elements that are not directly visible in your DOM. Traditionally this makes sense in order to keep the markup clean. But: "Utility-first" means "component second". And the component abstraction allows us to put any element inside.

Take an external link for example: If you want to add a right arrow (→) after the link, traditionally you could do this with the `::after` selector:

```css
/*a*/.external-link {
  text-decoration: underline;
  color: red;
}
/*a*/.external-link::after {
  content: '\u2192';
  color: gray;
}
```

Nowadays, creating an `<ExternalLink>` component with Tailwind and React for example would look somewhat like this:

```jsx
const ExternalLink = ({ children, href }) => (
  <a href={href} classNames="underline text-red-500">
    {children}
    <span className="text-gray-300" aria-hidden="true">
      &#8594;
    </span>
  </a>
);
```

It is perfectly fine to add an additional element to your render function. Just remember to add an `aria-hidden` attribute to make the element invisible to screen readers and search robots, just like CSS pseudo-elements would be.

The component would be used like this:

```jsx
<ExternalLink href="https:://site/path">Link text</ExternalLink>
```

## Numeric operators (`nth-child()`) in Tailwind**

Next, let's have some fun with numbers: What if you want to style a list with alternating background colors? Traditionally with CSS, you would probably do something like this:

```css
li:nth-child(even) { background-color: gray; }
li:nth-child(off) { background-color: white; }
```

Well, Tailwind got you covered and provides variants for this:

```html
<li class="even:bg-gray-500 odd:bg-white">
```

Since `even:` and `odd:` variants are not enabled by default, you need to configure them in your `tailwind.config.js` file. Just remember that every variant will increase the size of your CSS output.

Similarly you may activate `first:` and `last:` variants if you want to target the first or last element like you would traditionally do with the CSS `:first-child` and `:last-child` selectors.

However, if you want to do special ones like `nth-child(myIndex)` or `nth-child(myFormula)` (e.g. `nth-child(3n+1)`), you won't find any Tailwind variants for that.

But: "Utility-first" also means "component second": You will almost certainly use a component abstraction for your Tailwind styles anyways – be it React, Angular, Vue, Svelte or anything else.

Having a component stitched together via JavaScript also means that you already have a place where to put programming logic. So if you don't want to increase your output CSS file size by adding Tailwind variants for every possible utility-class, you actually don't need to do so.

Take the list example: In React, you will probably use a `.map()` function to map over the list items. Just add the `index` as the second argument to your arrow function and use it to create booleans (flags) that you can use in your `classNames` array:

```jsx
const MyList = ({ items }) => {
  const renderListItems = (item, index) => {
    const isSecond = index === 1;
    const isThird = index === 2;
    const classNames = [
      isSecond && "bg-gray",
      isThird && "bg-white"
    ].join(" ");
    return <li className={classNames}>{item.text}</li>;
  };
  return <ul>{items.map(renderListItems)}</ul>;
};
```

Admittedly, this example seems a lot more complex than the CSS one, but sometimes using JS logic gives you more flexibility than using CSS logic. This makes sense especially when you need to target the same elements in your JavaScript code anyway – why duplicate this logic in your CSS file? And with the utility-first approach this is perfectly fine as everything will be abstracted away into your component anyway.

Using `array` as the third argument of your map function you could also target the last element of your list like this:

```jsx
const MyList = ({ items }) => {
  const renderListItems = (item, index, array) => {
    const isFirst = index === 0;
    const isLast = index === array.length - 1;
    const classNames = [
      isFirst && "bg-gray",
      isLast && "bg-white"
    ].join(" ");
    return <li className={classNames}>{item.text}</li>;
  };
  return <ul>{items.map(renderListItems)}</ul>;
};
```

So you might not need to activate any needed variant in Tailwind. Just weigh CSS output size against JavaScript complexity. Not only when using JS to build static HTML pages the latter might even be an advantage.

So yes, you actually do not need any CSS pseudo-selector when using Tailwind and a component abstraction :)

## Concerns?

> But CSS rendering is quicker than JavaScript rendering!

Yes, that's true in most cases. especially for interactive states like `:active`, `:hover`, `:visited` – that's why I would always recommend using Tailwind variants for these.

But when it comes to pseudo-elements (`::before` and `::after`) or numeric operators (`:nth-of-child`, `::last-of-type`, ...), it doesn't make a difference, because you are already using JavaScript for rendering the component and mapping over its children anyway. In fact it's probably even quicker as you don't need to rely on CSS overwrites and can avoid CSS specificity issues.

> But I don't like to flood my component's markup with thousands of classnames!

Well, stay tuned for my next post with strategies for structuring Tailwind classes then ;)