Back to Tailwindcss

Index

src/blog/tailwindcss-v3-2/index.mdx

latest26.0 KB
Original Source

import { Example } from "@/components/example.tsx"; import { Figure } from "@/components/figure.tsx"; import { Image } from "@/components/media"; import Link from "next/link"; import { CodeExample, js, css, html, ts, CodeExampleGroup, CodeBlock } from "@/components/code-example"; import card from "./card.jpg"; import { adamwathan } from "@/app/blog/authors";

export const meta = { title: "Tailwind CSS v3.2: Dynamic breakpoints, multi-config, and container queries, oh my!", description: "...and nested group support, aria-* variants, data-* variants, @supports support, and more.", date: "2022-10-19T15:30:00.000Z", authors: [adamwathan], image: card, excerpt: ( <> Well it's that time again! The time where we quickly go from{" "} <em>"I really have no idea what we could even add to a new Tailwind release"</em> to{" "} <em> "wow, well this is actually a ridiculous amount of new stuff — we better tag a release before things get completely out of hand" </em> . </> ), };

<Image alt="Tailwind CSS v3.2" src={card} />

Tailwind CSS v3.2 is here with an absolutely massive amount of new stuff, including support for dynamic breakpoints, multiple config files in a single project, nested groups, parameterized variants, container queries, and more.

As always check out the <Link href="https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.2.0">release notes</Link> for every nitty-gritty fix and improvement, but here's the highlight reel:

  • <Link href="#multiple-config-files-in-one-project-using-config"> Multiple config files in one project using `@config` </Link>
  • <Link href="#browser-support-based-styling-with-supports">Browser-support-based styling with `supports-*`</Link>
  • <Link href="#aria-attribute-variants">ARIA attribute variants</Link>
  • <Link href="#data-attribute-variants">Data attribute variants</Link>
  • <Link href="#max-width-and-dynamic-breakpoints">Max-width and dynamic breakpoints</Link>
  • <Link href="#dynamic-group-and-peer-variants">Dynamic `group-*` and `peer-*` variants</Link>
  • <Link href="#dynamic-variants-with-match-variant">Dynamic variants with `matchVariant`</Link>
  • <Link href="#nested-group-and-multiple-peer-support-using-variant-modifiers"> Nested `group` and multiple `peer` support using variant modifiers </Link>
  • <Link href="#container-queries">Container queries</Link>

Upgrade your projects by installing the latest version of tailwindcss from npm:

sh
npm install -D tailwindcss@latest

Or play with the new features in <Link href="https://play.tailwindcss.com">Tailwind Play</Link> where you can try everything out instantly, right in the browser.


Multiple config files in one project using @config

We've added a new @config directive that you can use in a CSS file to specify which Tailwind CSS config to use for that file:

<CodeExampleGroup filenames={["./styles/admin.css", "./styles/client.css"]}> <CodeBlock example={js @config "./tailwind.admin.config.js"; @tailwind base; @tailwind components; @tailwind utilities; } /> <CodeBlock example={js @config "./tailwind.client.config.js"; @tailwind base; @tailwind components; @tailwind utilities; } /> </CodeExampleGroup>

This makes it a lot easier to build multiple stylesheets in a single project that have separate Tailwind configurations. For example, you might have one config file for the customer-facing part of your site, and another config for the admin/backend area.

You've always technically been able to do this with enough webpack wizardry, but the new @config directive makes it super easy and accessible to everyone, even in projects where you don't have as much control over the build tool configuration.


Browser-support-based styling with supports-*

You can now conditionally style things based on whether a certain feature is supported in the user's browser with the supports-[...] variant, which generates @supports rules under the hood.

html
<!-- [!code word:supports-[display\:grid\]\:grid] -->
<div class="flex supports-[display:grid]:grid ...">
  <!-- ... -->
</div>

The supports-[...] variant takes anything you'd use with @supports (...) between the square brackets, like a property/value pair, and even expressions using and and or.

If you only need to check if a property itself is supported, you can even just specify the property name and Tailwind will fill in the blanks for you:

html
<!-- [!code word:supports-[backdrop-filter\]\:bg-black/25] -->
<!-- [!code word:supports-[backdrop-filter\]\:backdrop-blur] -->
<div class="bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur ...">
  <!-- ... -->
</div>

ARIA attribute variants

You can now conditionally style things based on ARIA attributes with the new aria-* variants.

For example, you can update the background color of an element based on whether the aria-checked state is true:

html
<!-- [!code word:aria-checked\:bg-blue-600] -->
<span class="bg-gray-600 aria-checked:bg-blue-600" aria-checked="true" role="checkbox">
  <!-- ... -->
</span>

By default we've included modifiers for the most common boolean ARIA attributes:

{

<table> <thead> <tr> <th>Modifier</th> <th>CSS</th> </tr> </thead> <tbody> <tr> <td> <code className="before:content-none after:content-none">aria-checked</code> </td> <td> <code className="whitespace-nowrap before:content-none after:content-none"> <span className="text-slate-400">&</span>[aria-checked="true"] </code> </td> </tr> <tr> <td> <code className="before:content-none after:content-none">aria-disabled</code> </td> <td> <code className="whitespace-nowrap before:content-none after:content-none"> <span className="text-slate-400">&</span>[aria-disabled="true"] </code> </td> </tr> <tr> <td> <code className="before:content-none after:content-none">aria-expanded</code> </td> <td> <code className="before:content:none whitespace-nowrap after:content-none"> <span className="text-slate-400">&</span>[aria-expanded="true"] </code> </td> </tr> <tr> <td> <code className="before:content:none after:content-none">aria-hidden</code> </td> <td> <code className="whitespace-nowap before:content:none after:content-none"> <span className="text-slate-400">&</span>[aria-hidden="true"] </code> </td> </tr> <tr> <td> <code className="before:content-none after:content-none">aria-pressed</code> </td> <td> <code className="whitespace-nowrap before:content-none after:content-none"> <span className="text-slate-400">&</span>[aria-pressed="true"] </code> </td> </tr> <tr> <td> <code className="before:content-none after:content-none">aria-readonly</code> </td> <td> <code className="whitespace-nowrap before:content-none after:content-none"> <span className="text-slate-400">&</span>[aria-readonly="true"] </code> </td> </tr> <tr> <td> <code className="before:content-none after:content-none">aria-required</code> </td> <td> <code className="before:content:none whitespace-nowrap after:content-none"> <span className="text-slate-400">&</span>[aria-required="true"] </code> </td> </tr> <tr> <td> <code className="before:content:none after:content-none">aria-selected</code> </td> <td> <code className="whitespace-nowap before:content:none after:content-none"> <span className="text-slate-400">&</span>[aria-selected="true"] </code> </td> </tr> </tbody> </table> }

You can customize which aria-* modifiers are available by editing theme.aria or theme.extend.aria in your tailwind.config.js file:

js
// [!code filename:tailwind.config.js]
module.exports = {
  theme: {
    extend: {
      aria: {
        asc: 'sort="ascending"',
        desc: 'sort="descending"',
      },
    },
  },
};

If you need to use a one-off aria modifier that doesn’t make sense to include in your theme, or for more complex ARIA attributes that take specific values, use square brackets to generate a property on the fly using any arbitrary value.

<Figure> <Example padding={false}> { <div className="py-8"> <table className="w-full border-collapse border-y border-slate-400 bg-white text-sm shadow-sm dark:border-slate-500 dark:bg-slate-800"> <thead className="bg-slate-50 dark:bg-slate-700"> <tr> <th className="group border border-slate-300 px-4 py-3 text-left font-semibold text-slate-900 first:border-l-0 last:border-r-0 dark:border-slate-600 dark:text-slate-200" aria-sort="ascending" > <span className="flex w-full items-center justify-between gap-2"> Invoice # <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" className="h-5 w-5 fill-slate-500 group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180" > <path fillRule="evenodd" d="M10 5a.75.75 0 01.75.75v6.638l1.96-2.158a.75.75 0 111.08 1.04l-3.25 3.5a.75.75 0 01-1.08 0l-3.25-3.5a.75.75 0 111.08-1.04l1.96 2.158V5.75A.75.75 0 0110 5z" clipRule="evenodd" /> </svg> </span> </th> <th className="border border-slate-300 px-4 py-3 text-left font-semibold text-slate-900 first:border-l-0 last:border-r-0 dark:border-slate-600 dark:text-slate-200"> Client </th> <th className="border border-slate-300 px-4 py-3 text-right font-semibold text-slate-900 first:border-l-0 last:border-r-0 dark:border-slate-600 dark:text-slate-200"> Amount </th> </tr> </thead> <tbody> <tr> <td className="border border-slate-300 px-4 py-3 text-slate-500 first:border-l-0 last:border-r-0 dark:border-slate-700 dark:text-slate-400"> #100 </td> <td className="border border-slate-300 px-4 py-3 text-slate-500 first:border-l-0 last:border-r-0 dark:border-slate-700 dark:text-slate-400"> Pendant Publishing </td> <td className="border border-slate-300 px-4 py-3 text-right text-slate-500 tabular-nums first:border-l-0 last:border-r-0 dark:border-slate-700 dark:text-slate-400"> $2,000.00 </td> </tr> <tr> <td className="border border-slate-300 px-4 py-3 text-slate-500 first:border-l-0 last:border-r-0 dark:border-slate-700 dark:text-slate-400"> #101 </td> <td className="border border-slate-300 px-4 py-3 text-slate-500 first:border-l-0 last:border-r-0 dark:border-slate-700 dark:text-slate-400"> Kruger Industrial Smoothing </td> <td className="border border-slate-300 px-4 py-3 text-right text-slate-500 tabular-nums first:border-l-0 last:border-r-0 dark:border-slate-700 dark:text-slate-400"> $545.00 </td> </tr> <tr> <td className="border border-slate-300 px-4 py-3 text-slate-500 first:border-l-0 last:border-r-0 dark:border-slate-700 dark:text-slate-400"> #102 </td> <td className="border border-slate-300 px-4 py-3 text-slate-500 first:border-l-0 last:border-r-0 dark:border-slate-700 dark:text-slate-400"> J. Peterman </td> <td className="border border-slate-300 px-4 py-3 text-right text-slate-500 tabular-nums first:border-l-0 last:border-r-0 dark:border-slate-700 dark:text-slate-400"> $10,000.25 </td> </tr> </tbody> </table> </div> } </Example>

<CodeExampleGroup filenames={['HTML', 'Generated CSS']}> <CodeBlock example={html <!-- [!code word:aria-[sort=ascending\]\:bg-[url('/img/down-arrow.svg')\]] --> <!-- [!code word:aria-[sort=descending\]\:bg-[url('/img/up-arrow.svg')\]] --> <table> <thead> <tr> <th aria-sort="ascending" class="aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')]" > Invoice # </th> <!-- ... --> </tr> </thead> <!-- ... --> </table> } /> <CodeBlock example={css` .aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')][aria-sort="ascending"] { background-image: url("/img/down-arrow.svg"); }

  .aria-\[sort\=descending\]\:bg-\[url\(\'\/img\/up-arrow\.svg\'\)\][aria-sort="descending"] {
  background-image: url("/img/up-arrow.svg");
  }
`}

/>

</CodeExampleGroup> </Figure>

ARIA state modifiers can also target parent and sibling elements using the group-aria-* and peer-aria-* modifiers:

<CodeExampleGroup filenames={['HTML', 'Generated CSS']}> <CodeBlock example={html <!-- [!code word:group-aria-[sort=ascending\]\:rotate-0] --> <!-- [!code word:group-aria-[sort=descending\]\:rotate-180] --> <table> <thead> <tr> <th aria-sort="ascending" class="group"> Invoice # <svg class="group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180" > <!-- ... --> </svg> </th> <!-- ... --> </tr> </thead> <!-- ... --> </table> } /> <CodeBlock example={js` .group[aria-sort="ascending"] .group-aria-[sort=ascending]:rotate-0 { --tw-rotate: 0deg; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); }

  .group[aria-sort="descending"] .group-aria-\[sort\=descending\]\:rotate-180 {
    --tw-rotate: 180deg;
    transform: translate(var(--tw-translate-x), var(--tw-translate-y))
    rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
    scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
  }

`} />

</CodeExampleGroup>

Data attribute variants

You can now conditionally style things based on data attributes with the new data-* variants.

Since there are no standard data-* attributes by definition, we only support arbitrary values out of the box, for example:

html
<!-- Will apply -->
<div data-size="large" class="data-[size=large]:p-8">
  <!-- ... -->
</div>

<!-- Will not apply -->
<div data-size="medium" class="data-[size=large]:p-8">
  <!-- ... -->
</div>

<!-- Generated CSS -->
<style>
  .data-\[size\=large\]\:p-8[data-size="large"] {
    padding: 2rem;
  }
</style>

You can configure shortcuts for common data attribute selectors you're using in your project under the data key in the theme section of your tailwind.config.js file:

js
// tailwind.config.js
module.exports = {
  theme: {
    data: {
      checked: 'ui~="checked"',
    },
  },
  // ...
};
html
<div data-ui="checked active" class="data-checked:underline">
  <!-- ... -->
</div>

These variants also work as group-* and peer-* variants like many other variants in the framework:

html
<div data-size="large" class="group">
  <div class="group-data-[size=large]:p-8">
    <!-- Will apply `p-8` -->
  </div>
</div>

<div data-size="medium" class="group">
  <div class="group-data-[size=large]:p-8">
    <!-- Will not apply `p-8` -->
  </div>
</div>

Max-width and dynamic breakpoints

We've added a new max-* variant that lets you apply max-width media queries based on your configured breakpoints:

html
<div class="max-lg:p-8">
  <!-- Will apply `p-8` until the `lg` breakpoint kicks in -->
</div>

As a general rule I would still recommend using min-width breakpoints personally, but this feature does unlock one useful workflow benefit which is not having to undo some style at a different breakpoint.

For example, without this feature you often end up doing things like this:

html
<div class="md:sr-only xl:not-sr-only">
  <!-- ... -->
</div>

With this feature, you can avoid undoing that style by stacking a max-* variant on the original declaration:

html
<div class="md:max-xl:sr-only">
  <!-- ... -->
</div>

Along with this, we've added support for arbitrary values, and a new min-* variant that only accepts arbitrary values, so you can do things like this:

html
<div class="min-[712px]:max-[877px]:right-16 ...">
  <!-- ... -->
</div>

It's important to note that these features will only be available if your project uses a simple screens configuration.

These features are a lot more complicated than they look due to needing to ensure that all of these media queries are sorted in the final CSS in a way that gives you the expected behavior in the browser. So for now, they will only work if your screens configuration is a simple object with string values, like the default configuration:

js
// tailwind.config.js
module.exports = {
  theme: {
    screens: {
      sm: "640px",
      md: "768px",
      lg: "1024px",
      xl: "1280px",
      "2xl": "1536px",
    },
  },
};

If you have a complex configuration where you already have max-width breakpoints defined, or range-based media queries, or anything other than just strings, these features won't be available. We might be able to figure that out in the future but it just creates so many questions about how the CSS should be ordered that we don't have answers for yet.

So for now (and possibly forever), if you want to use these features, your screens configuration needs to be simple. My hope is that these features make complex screens configurations unnecessary anyways.


Dynamic group-* and peer-* variants

It's now possible to create custom group-* and peer-* variants on the fly by passing your own selector to be "groupified" or "peerified" between square brackets:

<CodeExampleGroup filenames={['HTML', 'Generated CSS']}> <CodeBlock example={html <!-- [!code word:group-[.is-published\]\:block] --> <div class="group is-published"> <div class="group-[.is-published]:block hidden">Published</div> </div> } /> <CodeBlock example={css .group.is-published .group-\[\.is-published\]\:block { display: block; } } /> </CodeExampleGroup>

For more control, you can use the & character to mark where .group or .peer should end up in the final selector relative to the selector you are passing in:

<CodeExampleGroup filenames={['HTML', 'Generated CSS']}> <CodeBlock example={html <!-- [!code word:peer-[\:nth-of-type(3)_&\]\:block] --> <div> <input type="text" class="peer" /> <div class="peer-[:nth-of-type(3)_&]:block hidden"> <!-- ... --> </div> </div> } /> <CodeBlock example={css :nth-of-type(3) .peer ~ .peer-\[\:nth-of-type\(3\)_\&\]\:block { display: block; } } /> </CodeExampleGroup>

Let's be serious you're probably going to use these features like three times in your entire life but it's still pretty cool. Hoping we can use this as a building block to make group and peer work more automatically with variants registered by third-party plugins in the future.


Dynamic variants with matchVariant

You've probably noticed this new variant-[...] syntax in a lot of these new features — this is all powered by a new matchVariant plugin API that makes it possible to create what we're calling "dynamic variants".

Here's an example of creating a placement-* variant for some imaginary tooltip library that uses a data-placement attribute to tell you where the tooltip is currently positioned:

js
// [!code word:js]
let plugin = require("tailwindcss/plugin");

module.exports = {
  // ...
  plugins: [
    plugin(function ({ matchVariant }) {
      matchVariant(
        "placement",
        (value) => {
          return `&[data-placement=${value}]`;
        },
        {
          values: {
            t: "top",
            r: "right",
            b: "bottom",
            l: "left",
          },
        },
      );
    }),
  ],
};

The variant defined above would give you variants like placement-t and placement-b, but would also support the arbitrary portion in square brackets, so if this imaginary tooltip library had other potential values that you didn't feel the need to create built-in values for, you could still do stuff like this:

html
<div class="placement-[top-start]:mb-2 ...">
  <!-- ... -->
</div>

When defining a custom variant with this API, it's often important that you have some control over which order the CSS is generated in to make sure each class has the right precedence with respect to other values that come from the same variant. To support this, there's a sort function you can provide when defining your variant:

js
// [!code word:js]
matchVariant("min", (value) => `@media (min-width: ${value})`, {
  sort(a, z) {
    return parseInt(a) - parseInt(z);
  },
});

Nested group and multiple peer support using variant modifiers

Sometimes you can run into problems when you have multiple group chunks nested within each other because Tailwind has no real way to disambiguate between them.

To solve this, we're adding support for variant modifiers, which are a new dynamic chunk that you can add to the end of a variant (inspired by our optional opacity modifier syntax) that you can use to give each group/peer your own identifier.

Here's what it looks like:

html
<!-- [!code word:group/sidebar] -->
<!-- [!code word:group/navitem] -->
<!-- [!code word:group-hover/sidebar\:opacity-75] -->
<!-- [!code word:group-hover/navitem\:bg-black/75] -->
<div class="group/sidebar ...">
  <!-- ... -->
  <div class="group/navitem ...">
    <a href="#" class="opacity-50 group-hover/navitem:bg-black/75 group-hover/sidebar:opacity-75">
      <!-- ... -->
    </a>
  </div>
  <!-- ... -->
</div>

This lets you give each group a clear name that makes sense for that context on the fly, and Tailwind will generate the necessary CSS to make it work.

I'm really excited to have a solution out there for this because it's something I've been trying to land on a good approach for solving for several years, and this is the first thing we've come up with that really feels like it offers the power and flexibility I think it should.


Container queries

I can barely believe it but container queries are finally real and the browser support is dangerously close to making these ready for production — in fact if you're building an Electron app you could use these today.

Today we're releasing @tailwindcss/container-queries which is a new first-party plugin that adds container query support to the framework, using a new @ syntax to differentiate them from normal media queries:

html
<div class="@container">
  <div class="block @lg:flex">
    <!-- ... -->
  </div>
</div>

Out-of-the-box we include a set of container sizes that match our default max-width scale:

{

<table> <thead> <tr> <th>Name</th> <th>Value</th> </tr> </thead> <tbody> <tr> <td>xs</td> <td>20rem</td> </tr> <tr> <td>sm</td> <td>24rem</td> </tr> <tr> <td>md</td> <td>28rem</td> </tr> <tr> <td>lg</td> <td>32rem</td> </tr> <tr> <td>xl</td> <td>36rem</td> </tr> <tr> <td>2xl</td> <td>42rem</td> </tr> <tr> <td>3xl</td> <td>48rem</td> </tr> <tr> <td>4xl</td> <td>56rem</td> </tr> <tr> <td>5xl</td> <td>64rem</td> </tr> <tr> <td>6xl</td> <td>72rem</td> </tr> <tr> <td>7xl</td> <td>80rem</td> </tr> </tbody> </table> }

You can configure which values are available using the containers key in your tailwind.config.js file:

js
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      containers: {
        "2xs": "16rem",
        // etc...
      },
    },
  },
};

We also include support for arbitrary values, using the @[...] syntax:

html
<div class="@container">
  <div class="block @[618px]:flex">
    <!-- ... -->
  </div>
</div>

...and named containers using the same variant modifier syntax we're now shipping for group-* and peer-* variants:

html
<div class="@container/main">
  <!-- ... -->
  <div>
    <div class="block @lg/main:flex">
      <!-- ... -->
    </div>
  </div>
</div>

Right now we're starting with simple min-width based container queries, but we plan to expand the scope over time, and when it feels like we've really nailed the APIs we'll bring it all into core.

For complete documentation, check out the plugin <Link href="https://github.com/tailwindlabs/tailwindcss-container-queries">on GitHub</Link>.


So there you have it — Tailwind CSS v3.2! Major improvements but just a minor version change, so no breaking changes and you should be able to update your project by just updating your dependency:

sh
npm install -D tailwindcss@latest

Yeah I hear you in the back, still no text shadows, but hey at least you can style the sibling of a checkbox when the checkbox's parent is the third child in a list without leaving your HTML. Priorities people.