Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v4] "hidden" is not the last display utility anymore #15884

Closed
iquito opened this issue Jan 26, 2025 · 18 comments
Closed

[v4] "hidden" is not the last display utility anymore #15884

iquito opened this issue Jan 26, 2025 · 18 comments

Comments

@iquito
Copy link

iquito commented Jan 26, 2025

What version of Tailwind CSS are you using?

v4.0

Reproduction URL

https://play.tailwindcss.com/1XH8TJa6s3 for v4 (element is shown)
https://play.tailwindcss.com/Ajm578zcYz for v3.4.15 (element is not shown)

Describe your issue

The order of the display utilities has slightly changed in v4, putting hidden somewhere in the middle of these utilities by default (after block, but before inline-block or inline-flex), where in v3.4 it was at the end of all the display utilities. I have used TailwindCSS often like this:

<div class="inline-block hidden">Some text that is either displayed as inline-block or hidden</div>

Then I remove or re-add the hidden class with Javascript, which worked fine in v3. In v4 this does not work with inline-block anymore (because inline-block comes after hidden in the generated CSS), while it does work with block (as it comes before hidden). I found the behavior better in v3.4, where hidden was assumed to be the preferred/overriding value (if set) of all the display utilities and comes last in the definitions. I could imagine many people used hidden in this way and will find the new order surprising.

@wongjn
Copy link
Contributor

wongjn commented Jan 26, 2025

You shouldn't ever have two utilities that apply the same CSS property in the same context, because -as you have found - order is not guaranteed. Consider toggling between them.

@iquito
Copy link
Author

iquito commented Jan 26, 2025

With any other CSS property I would agree with you, but with hidden it is a convenient way to hide an element that could have any other display utility class at the moment. If you want to toggle, you would need to keep track of the original display class, instead of just being able to add or remove hidden (and not knowing anything about the element itself or how it is being displayed otherwise), so I think being able to add hidden and it coming last in the CSS definitions has some value, and with past TailwindCSS versions this has been the case.

As I am already using v4 for my project, I have "solved" this problem with a zero-width-breakpoint-version of hidden, but as more people switch to v4 I could imagine that this could cause some subtle breaks in applications that assumed the same thing as me and thought Tailwind would keep hidden as the last display utility class.

@wongjn
Copy link
Contributor

wongjn commented Jan 26, 2025

You could use the hidden attribute as an alternative - if you are using Tailwind's Preflight, it would take precendence over any other display utility.

@iquito
Copy link
Author

iquito commented Jan 26, 2025

Interesting, thanks for the tip, that might be a good alternative.

@philipp-spiess
Copy link
Member

@iquito Were you able to make it work with the hidden attribute?

@iquito
Copy link
Author

iquito commented Jan 28, 2025

@philipp-spiess Yes, the hidden attribute is a good way to solve it and maybe some additional documentation about it would be helpful (so far I didn't find anything about it in the docs, it is also not mentioned in the preflight explanation).

It was quite a bit of work though even in my not-so-huge application, so changing the order of the hidden class to come last in the CSS like in v3 would avoid this possible BC break for applications doing something similar without having to do a lot of changes, therefore I still think it would make sense to avoid this pain point for other users upgrading to v4 while also adjusting the docs to promote using the hidden attribute for this kind of behavior from now on.

@finkrer
Copy link

finkrer commented Jan 30, 2025

My Button component is inline-flex inside. I used hidden on the component to hide it conditionally. The fact that it is technically the same property... well, let's just say Tailwind class names, it being inside of the component, and it working just fine made it easy to forget about that.

Now, first, this is a breaking change, and I honestly don't know where it might pop up where I haven't noticed it yet.

Second, how do I do this now? Or even, how was I supposed to do it before? The hidden attribute is cool, but what about lg:hidden? Using the Tailwind breakpoints was the whole reason to use hidden the utility class to hide stuff. Do I have to use JS to get the screen width, compare it to a breakpoint, and set the hidden attribute accordingly? Nah, that's too much work.

I guess from now on it's lg:hidden!. But if you need to hide for the smaller breakpoint, it's hidden! lg:block!. Not pretty, still better than JS though.

@RobinMalfait
Copy link
Member

@finkrer the problem only exists if you set the inline-flex and hidden class at the same time unconditionally. If you have inline-flex lg:hidden that still works as expected because they don't have the same "specificity" in this case.

E.g.: https://play.tailwindcss.com/dvYE6VFc7T

This kind of issue already existed in v3, especially because unfortunately class order in the HTML doesn't matter.

<div class="text-red-500 text-blue-500"></div>
<div class="text-blue-500 text-red-500"></div>

E.g.: https://play.tailwindcss.com/d2zsKyorBH

If you use the VSCode Intellisense plugin then you should see some squiggles (as you can see in the Tailwind Play link). If you use the prettier plugin, then we sort the classes to be consistent and to reduce the amount of potential issues you run into.

@adrianthedev
Copy link

I feel @finkrer's issue too.

I have a button component which has inline-flex so it properly positions the elements inside of it.
Sometimes I render this button with hidden lg:inline-flex classes added so it's hidden o mobile, but visible on larger screens.

It's a common practice and pattern to be able to do that so easily and have hidden be "God" and be the most specific one.

It's a breaking change which, if it can be refactored, could be reverted easily (famous last words).

Thanks for all your work 🙌

@adrianthedev
Copy link

I know that we could use the important modifier (!hidden) here and there, but my hunch is that it's going to break a ton of libraries that are hiding elements using the hidden class (I think Tip Tap does something like that).

Even if they upgrade to TW 4, now they have to choose from !hidden or hidden!.
It's not going to be pretty.

@Mr-Zafar
Copy link

Mr-Zafar commented Feb 1, 2025

Is there any specific reasoning behind this decision? I mean, there are hundreds of millions of projects; do you expect everyone to now rebuild their code structure to match the new order? It seems to me that this is a big breaking change that wasn't properly addressed in the upgrade guide.

@chrisreddington
Copy link

chrisreddington commented Feb 3, 2025

I've encountered the same on one of my own projects. I was using it to facilitate manual switching between a Light/Dark mode (and hide/display the icon in the button).

It's an easy enough change in my codebase to replace with hidden! (per the above discussion), though not ideal. Going to dig into the rest of my codebase to determine any other impacts.

@philipp-spiess
Copy link
Member

Hey folks! We decided to not change the order of display properties to make hidden appear last for now.

I know some of you have depended on this for your product code but we really recommend using either the HTML hidden attribute or to conditionally only show the relevant class names. E.g. in React you should do something like this to never have two conflicting position utilities on the same node:

function Button({ show }: { show: boolean }) {
  return <button className={show ? "block" : "hidden"}>Click me</button>;
}

Furthermore, this pattern was always something that our IntelliSense extension has warned about:

Image

Since this technically was undefined behavior for now, we recommend you migrate away from this instead. Thanks for understanding.

@lmatiolis
Copy link

Hi @philipp-spiess , thanks for clarifying the current situation on the issue.

I wanted to add that the suggestion to use the HTML hidden attribute is somewhat misleading because it also has 2 different behaviors on Tailwind 3.4 and Tailwind 4. I started the migration path here in my application only to find out that I can't incrementally switch my code to use the hidden attribute to then switch to v4.

Example:

On Tailwind 4, the hidden attribute is prioritized like your suggestion:
Image

On Tailwind 3, the hidden is ignored because of the flex class:
Image

It looks like this was a known thing on v3: #9979

Anyway, just wanted to share this and mention that I'm still unsure of what path I should take... it sounds like the recommended way is to really avoid having 2 classes that control the display at the same time, but that's going to be a big change here since we were making use of the tailwind hidden class.

Thanks again!

@adrianthedev
Copy link

Hey @philipp-spiess.

Thanks for your response.

  1. We don't all work with React and it's not trivial to create components like that one your exemplified.
    Some apps (we run Rails and use Hotwire) rely on that pattern of using the hidden class as the main modifier even though we have a regular flex or grid display marker that sets the way that element should be when it's not hidden.

  2. There are other libraries that use the hidden class in a similar fashion. Most are plain javascript and the only toggle they run is to add/remove the hidden class from elements.

Please reconsider this change as it impacts the whole web ecosystem and it's not just a matter of toggling the class in a React component.

Thanks!

@koddsson
Copy link

Furthermore, this pattern was always something that our IntelliSense extension has warned about:

Can this linter standalone instead of in VSCode? Some of our engineers don't run VSCode, and it would be great to catch this before hitting production. Running this in CI or as part of our suite of linters would be great.

@staaky
Copy link

staaky commented Feb 16, 2025

You shouldn't ever have two utilities that apply the same CSS property in the same context, because -as you have found - order is not guaranteed. Consider toggling between them.

Furthermore, this pattern was always something that our IntelliSense extension has warned about

Having two display classes on the same element overriding each other is perfectly fine in CSS. It's not optimal in the same context, so an IntelliSense warning makes sense, but I wouldn't introduce a breaking change like this because of it. In practice, it's more likely that hidden is applied in a different context, to a component that already has a base display class, so there will be no warnings:

// renders as `inline-block hidden`, without IntelliSense warning
<Component className={'hidden'} />

function Component({ className, ...rest }) {
    return <div className={classNames('inline-block', className)} {...rest} />;
}

So having hidden last helped with composability. If the breaking change is permanent, perhaps npx @tailwindcss/upgrade should add a note with a workaround for it, so people can either stick with it or make changes and remove it.

@import 'tailwindcss';

/*
  Note on breaking change in Tailwind CSS v4...
*/
.hidden {
   display: none;
}

@David05500
Copy link

David05500 commented Feb 17, 2025

I use Nextjs 15, and hidden even overrides the md:flex, thats well strange!
<div className="hidden md:flex">

My work around was to add the exclamation to md:!flex, which is wrong.
Am I missing something? Is there a different way to hide an element in v4?

@philipp-spiess philipp-spiess marked this as a duplicate of #16586 Feb 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests