Tailwind `&` Selector

After noticing the & selector being used in shadcn/ui components, I decided to do some digging to try and understand how it works. Below are some of the instances the selector may come in handy.

Targeting a Child Element

A simple example is when building a reusable button component for a design system and you want to ensure that any icons placed inside the button follow a consistent style by default.

Here’s how shadcn achieves this:

import * as React from "react";
import { cn } from "@/lib/utils";

export const Button = ({
  className,
  ...props
}: React.ComponentProps<"button">) => {
  return (
    <button
      className={cn(
        "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0",
        className
      )}
      {...props}
    />
  );
};
an oversimplication of how shadcn does it

What’s happening here is that all <svg> icons inside the button automatically get a default size of 16px (the Tailwind size-4 class) and have pointer events disabled. In this case, the & acts as a reference to the current element, in this example, the button itself. So when you see [&_svg]:pointer-events-none, Tailwind is essentially generating a CSS rule that targets any <svg> inside that button.

In plain CSS, it would look like this:

button svg {
  pointer-events: none;
}

Targeting Sibling elements

Here’s a simple example, checking the checkbox changes the opacity of the text next to it.

Click the checkbox

Here’s the code:

const CheckboxExample = () => {
  return (
    <label className="...">
      <input type="checkbox" className="checkbox" />
      <small className="opacity-70 [.checkbox:checked+&]:opacity-100]">
        Agree to terms and conditions
      </small>
    </label>
  );
};

In this case, the selector [.checkbox:checked+&] checks if the checkbox is in a checked state and then applies styles to the element that immediately follows it , the <small> text.

Using Data attributes

I talked about data attributes in a previous article . Here’s a quick example that uses them to animate an icon inside a Radix accordion trigger:

<AccordionTrigger className="data-[state=open]:[&_.icon]:rotate-45">
  <PlusIcon className="icon transition-transform duration-300" />
</AccordionTrigger>

Targeting Nested Elements

The & selector also comes in handy when you want to style nested elements. For example, say you want to target a grandchild element. You can do that easily like this:

Nested opacity change on hover

and here’s the code:

<div className="hover:[&_.wrapper_.text]:opacity-50">
  <div className="wrapper">
    <p className="text">Nested opacity change on hover</p>
  </div>
</div>

Winding up

In summary, & gives you control over how styles are applied in relation to the current element. You can use it to target the element itself, its states (like &:hover), its children ([&_span]), siblings ([&+p]), or even complex combinations of nested selectors.

454 words

© 2023. All rights reserved.