-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Tailwind CSS v3.2 Beta 2 #9574
-
Hey folks! We've made some changes and improvements since the last Tailwind CSS v3.2 pre-release and I'd love your help testing this stuff for a few days before we (hopefully!) tag the final release later this week. If you've already been testing since the first pre-release, you can read about just the changes since that release, but here's an overview of all of the new stuff coming in v3.2 in its latest form:
To install the latest pre-release, install our insiders build from npm: npm install -D tailwindcss@insiders You can also try the new features directly in Tailwind Play by choosing "Insiders" from the version dropdown, but it's better for us if you actually install it in your project so we can get more information about how all this stuff is going to work alongside different UI libraries, templating languages, and build tools. Multiple config files in one project using
|
Variant | CSS |
---|---|
aria-checked: |
[aria-checked="true"] |
aria-disabled: |
[aria-disabled="true"] |
aria-expanded: |
[aria-expanded="true"] |
aria-hidden: |
[aria-hidden="true"] |
aria-pressed: |
[aria-pressed="true"] |
aria-readonly: |
[aria-readonly="true"] |
aria-required: |
[aria-required="true"] |
aria-selected: |
[aria-selected="true"] |
For more complex ARIA attributes that take specific values (like aria-activedescendant
), you can use the arbitrary value/square bracket notation to create custom aria-*
variants on the fly:
<th aria-sort="ascending" class="aria-[sort=ascending]:bg-[url('/img/up-arrow.svg')] aria-[sort=descending]:bg-[url('/img/down-arrow.svg')]" > <!-- ... --> </th>
You can customize which aria-*
variants are available by editing theme.aria
or theme.extend.aria
in your tailwind.config.js
file:
module.exports = { theme: { extend: { aria: { asc: 'sort="ascending"', desc: 'sort="descending"', }, }, }, };
Then you can use any custom aria-*
variants you've configured in your project:
<th aria-sort="ascending" class="aria-asc:bg-[url('/img/up-arrow.svg')] aria-desc:bg-[url('/img/down-arrow.svg')" > <!-- ... --> </th>
These variants also work as group-*
and peer-*
variants like many other variants in the framework:
<div>Message:</div> <div role="textbox" contenteditable aria-required="true" class="peer"></div> <div class="hidden peer-aria-required:block">A message is required.</div>
New variants for data-*
attributes
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:
<!-- 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:
// tailwind.config.js module.exports = { theme: { data: { checked: 'ui~="checked"', }, }, // ... };
<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:
<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're trying out a way to support range-based media queries as well as dynamic/arbitrary breakpoints for situations where you just need to make a very specific tweak at a specific viewport size.
We've added a new max-*
variant that lets you apply max-width media queries based on your configured breakpoints:
<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:
<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:
<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:
<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:
// 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
We're making it possible to create custom group-*
and peer-*
variants on the fly by passing your selector between square brackets
<div> <!-- ... --> <a class="peer" href="#">...</a> <a class="peer-[:local-link]:ml-3"> <!-- ... --> </a> <!-- ... --> </div>
Whatever you pass will just get tacked on to the end of the .group
or .peer
class in the selector by default, but if you need to do something more complex you can use the &
character to mark where .group
/.peer
should end up in the final selector relative to the selector you are passing in
<div class="group"> <div class="group-[:nth-of-type(3)_&]:border-black"> <!-- ... --> </div> </div>
Parameterized variants with matchVariant
Both the new supports-[..]
feature and dynamic group-*
/peer-*
variants are powered by a new matchVariant
plugin API that you can use to create your own dynamic/parameterized 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:
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:
<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:
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:
<div class="group/sidebar ..."> <!-- ... --> <div class="group/navitem ..."> <a href="#" class="opacity-50 group-hover/sidebar:opacity-75 group-hover/navitem:bg-black/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.
Changes since the last pre-release
Since the last pre-release, we've added three new features:
- New variants for
aria-*
attributes - New variants for
data-*
attributes - Max-width and dynamic breakpoints
The only change to any existing new features is that we've dropped the angle-brackets <label>
syntax for what we're now calling variant modifiers:
- <div class="group<sidebar> ..."> + <div class="group/sidebar ..."> <!-- ... --> - <div class="group<navitem> ..."> + <div class="group/navitem ..."> <a href="#" - class="opacity-50 group<sidebar>-hover:opacity-75 group<navitem>-hover:bg-black/75" + class="opacity-50 group-hover/sidebar:opacity-75 group-hover/navitem:bg-black/75" > <!-- ... --> </a> </div> <!-- ... --> </div>
I'm excited about this change because the original implementation added this whole new "labels" concept which felt risky to me. The new implementation leans on the existing "modifier" syntax we already use for opacity modifiers, but brings it to variants.
So now we have this consistent "shape" to the components of a class name, and the idea of a "modifier" is a lot more generalized and useful in lots of situations, whereas "label" was very specific to this group/peer problem. We're going to expose the modifier in the plugin API for both variants and utilities, so your own plugins can use the modifier for whatever you want — it basically just becomes a second parameter like opacity is for a color utility.
So that's everything! Please test the hell out of it and let us know what you think before it's too late 😄
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 5 -
🎉 59 -
❤️ 12 -
🚀 44
Replies: 16 comments 19 replies
-
Love the Max-width and dynamic breakpoints
changes
Beta Was this translation helpful? Give feedback.
All reactions
-
Super excited about the @config
feature. We have an application that runs multiple sites, and allows each site to pick its theme. Up until now, they've all had to share the same config, and just utilize them differently in their views, but this should give us the ability to customize even more!
Beta Was this translation helpful? Give feedback.
All reactions
-
What is the generated CSS for md:max-xl:sr-only
?
Beta Was this translation helpful? Give feedback.
All reactions
-
It would generate exactly what you asked for:
@media (min-width: 768px) { @media not all and (min-width: 1280px) { .md\:max-xl\:sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; } } }
So this now applies sr-only
between the 768px
and 1280px
range.
Beta Was this translation helpful? Give feedback.
All reactions
-
🚀 1
-
Thanks! I was asking because I wasn't sure if I should expect nested media queries or a single combined one because I'm old school and hadn't caught up with the browser support, but it's all good nowadays.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 4
-
Really excited for @config and for nested groups. Felt the need for both last week 🙌🙌
Beta Was this translation helpful? Give feedback.
All reactions
-
This all looks incredible! Great work on this. Y'all killed it on this release 🙌🏼
Beta Was this translation helpful? Give feedback.
All reactions
-
Would it be too late in the game to include the existing not-
PR in this release?
I just checked up on it, and it's looking pretty refined: #7976
I commonly find myself reaching for negations more verbose and using something like this: [&:not(:hover)]:text-blue-500
. I'd love to use not-hover:text-blue-500
, which follows the existing pattern seen in other selectors already in Tailwind, as seen in the example above for not-sr-only
(though a variant).
This PR feels especially fitting for a change like that, categorically.
Beta Was this translation helpful? Give feedback.
All reactions
-
Won’t make it into this release I’m afraid but hoping to dig into it for Nov/Dec. I think we need a more generic/composable solution than what we have so far, which is what I’m hoping we can explore in the next few months.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
That sounds ideal. I'm looking forward to it! Thanks 🙌🏼
Beta Was this translation helpful? Give feedback.
All reactions
-
This is sooo good. Hyped for the nested group/peer modifiers. 🔥🔥🔥
Beta Was this translation helpful? Give feedback.
All reactions
-
🚀 1
-
For the matchVariant plugin API, is it possible to include a value for no parameter? Or would I use the addVariant API for that one and matchVariant for parameters?
EDIT: You can simply add a default value for the value
parameter
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", }, } ); }), ], };
Beta Was this translation helpful? Give feedback.
All reactions
-
This is not going to work as in your example. What you want to do is this instead:
matchVariant('foo', (value) => '.foo-${value} &', { values: { DEFAULT: 'bar' } })
This is the same API as matchUtilities
👍
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
Love this, it looks dope! <3
Beta Was this translation helpful? Give feedback.
All reactions
-
So great to see ARIA variants being added to Tailwind, awesome release!
Beta Was this translation helpful? Give feedback.
All reactions
-
I see good opportunity to implement nth-child
variant (not only odd and even Tailwind has but any) in a core with matchVariant
Beta Was this translation helpful? Give feedback.
All reactions
-
Is there a way to make a custom variant work with nested groups? For example, I often make a custom hocus:
variant that works with group-
and peer-
like this:
addVariant('hocus', [ '@media (hover: hover) and (pointer: fine) { &:hover }', '&:focus', ]); addVariant('group-hocus', [ '@media (hover: hover) and (pointer: fine) { :merge(.group):hover & }', ':merge(.group):focus &', ]); addVariant('peer-hocus', [ '@media (hover: hover) and (pointer: fine) { :merge(.peer):hover ~ & }', ':merge(.peer):focus ~ &', ]);
I'd like to be able to use group-hocus/sidebar:
, say.
Beta Was this translation helpful? Give feedback.
All reactions
-
👀 1
-
@MichaelAllenWarner any idea if this is supported yet? It'd be great to have.
Beta Was this translation helpful? Give feedback.
All reactions
-
No, I don't think so. I've been doing this sort of thing:
// hocus addVariant('hocus', [ '&:hover', '&:focus', ]); // group-hocus / group-hocus/{name} matchVariant( 'group', (_value, { modifier }) => modifier ? [ `:merge(.group\\/${modifier}):hover &`, `:merge(.group\\/${modifier}):focus &`, ] : [ `:merge(.group):hover &`, `:merge(.group):focus &`, ], { values: { hocus: 'hocus' } } ); // peer-hocus / peer-hocus/{name} matchVariant( 'peer', (_value, { modifier }) => modifier ? [ `:merge(.peer\\/${modifier}):hover ~ &`, `:merge(.peer\\/${modifier}):focus ~ &`, ] : [ `:merge(.peer):hover ~ &`, `:merge(.peer):focus ~ &`, ], { values: { hocus: 'hocus' } } ); // ... // make this the last custom plugin (see explanation below) const defaultGroupPeerVariants = { group: (_, { modifier }) => modifier ? [`:merge(.group\\/${escapeClassName(modifier)})`, ' &'] : [`:merge(.group)`, ' &'], peer: (_, { modifier }) => modifier ? [`:merge(.peer\\/${escapeClassName(modifier)})`, ' ~ &'] : [`:merge(.peer)`, ' ~ &'], }; for (const [name, fn] of Object.entries(defaultGroupPeerVariants)) { matchVariant(name, (value = '', extra) => { let result = normalize( typeof value === 'function' ? value(extra) : value ); if (!result.includes('&')) result = '&' + result; const [a, b] = fn('', extra); let start = null; let end = null; let quotes = 0; for (let i = 0; i < result.length; ++i) { const c = result[i]; if (c === '&') { start = i; } else if (c === "'" || c === '"') { quotes += 1; } else if (start !== null && c === ' ' && !quotes) { end = i; } } if (start !== null && end === null) { end = result.length; } // Basically this but can handle quotes: // result.replace(/&(\S+)?/g, (_, pseudo = '') => a + pseudo + b) return ( result.slice(0, start) + a + result.slice(start + 1, end) + b + result.slice(end) ); }); }
The big chunk of code at the end is copied from core Tailwind. It's needed because our custom group/peer matchVariant()
stuff overrides the core group/peer stuff, which matters for arbitrary group/peer variants (like group-[&.foo]:
). So to get Tailwind to process arbitrary group/peer variants correctly, we have to override our custom stuff with core stuff, if that makes sense. It's ugly but it works.
Beta Was this translation helpful? Give feedback.
All reactions
-
Oh, and the normalize()
and escapeClassName()
dependencies can be imported into the TW config like this:
const { default: escapeClassName } = require('tailwindcss/lib/util/escapeClassName'); const { normalize } = require('tailwindcss/lib/util/dataTypes');
To rephrase my answer to your question: no, as far as I know it isn't officially supported, but yes, it's possible (though awkward).
Beta Was this translation helpful? Give feedback.
All reactions
-
@MichaelAllenWarner Nice! That's pretty clever, importing escapeClassName
and normalize
from TailwindCSS like that. I might have to adopt that technique for some of my own plugins.
Specifically, I think that'll be especially useful if I get around to building my peer-group
variant idea as a plugin.
Currently, there exists no native way to do anything like uncle ~ parent uncle:checked { ... }
, which is what peer-group
strives to solve. I run into that issue at least once or twice a month and have to resort to using arbitrary variants.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
There's a much simpler solution to this that @thecrypticace explains here. You can just do:
matchVariant( 'group-hocus', (_value, { modifier }) => { /* ... */ }, { values: { DEFAULT: '' } } );
The magic sauce is the DEFAULT
key, which I didn't know about.
Beta Was this translation helpful? Give feedback.
All reactions
-
With named groups and peers now supported, I'd love to see a couple features added—
-
naming collisions between peers and groups. Personally, I think peers and groups should share the same namespace, so having an ancestor group and a previous peer of the same name would look at both as the same object, not two separate entities.
While this may sound like a limitation, it would actually allow Tailwind to integrate the two, which leads me to my next suggestion, re proposal Proposal for `peer-group` #9611 :
-
I'd really like to see added support for
peer-group
which would allow us to create groups that can act on a peer's state:WITHOUT
peer-group
(example):╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ◯━━┳━ peer ╭┄┄┄┴┄┄┄┄╮ ╰┄┤ requires introducing a non-utility ┆ ┗━ peer-checked:[&_.hideable]:collapse ╭┄┤ class to identify the element using ┆ ┗━ hideable ┆ ┆ a peer with an arbitrary variant ┆ ╰┄┄┄┬┄┄┄┄╯ ┆ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
WITH
peer-group
(example of code, preview not supported):◯━━┳━ peer/toggle ┗━ peer-group/toggle ┗━ peer-group-checked/toggle:collapse
I explain all the rationale for this in the full proposal (Proposal for `peer-group` #9611 ).
Beta Was this translation helpful? Give feedback.
All reactions
-
Has anyone found that height, width and background has stopped functioning properly with this update?
Beta Was this translation helpful? Give feedback.
All reactions
-
@Jae-Kae Are you still experiencing that? You should open a bug report so the team can address it.
Beta Was this translation helpful? Give feedback.
All reactions
-
Hi there, does the new "max" breakpoints work with the @screen
directive?
This was our previous configuration:
screens: { xs: '420px', sm: '576px', md: '829px', lg: '992px', xl: '1200px', '-xl': { max: '1199px' }, '-lg': { max: '991px' }, '-md': { max: '828px' }, '-sm': { max: '575px' }, '-xs': { max: '419px' }, }
I'd like to replace it with the new max-
breakpoints:
screens: { xs: '420px', sm: '576px', md: '829px', lg: '992px', xl: '1200px', }
But it seems like @screen
directive is not following the new convention because I get this error:
The 'max-sm' screen does not exist in your theme:
@screen max-sm {
@apply ...
Did I miss something? Is it unsupported on purpose?
Beta Was this translation helpful? Give feedback.
All reactions
-
@nicooprat Is it possible that you have a plugin installed that was building your previous max breakpoints, which is now preventing the new ones from being automatically generated?
Beta Was this translation helpful? Give feedback.
All reactions
-
Nothing I can think of...
I did a very simple reproduction in Tailwind Playground: https://play.tailwindcss.com/f7dREBB6Fn (in the HTML, max-md:hidden
works though)
Beta Was this translation helpful? Give feedback.
All reactions
-
Hmm yeah I'm not quite sure. I'm curious of @tailwindlabs' take on this, though this might be a better discussion for the official Tailwind CSS discord server: https://tailwindcss.com/discord
Beta Was this translation helpful? Give feedback.
All reactions
-
- to the comments above.
For some reason, after upgrading to 3.2.4, dynamic breakpoints are not working for me anymore.
Things like max-md:hidden
or max-lg:p-4
Beta Was this translation helpful? Give feedback.