tailwindcss icon indicating copy to clipboard operation
tailwindcss copied to clipboard

`dark` modifier in HTML can cause circular dependency issue

Open simonhmadsen opened this issue 2 years ago • 4 comments

What version of Tailwind CSS are you using?

v.3.0.23

What build tool (or framework if it abstracts the build tool) are you using?

postcss-cli 8.4.7

What version of Node.js are you using?

v14.18.0

What browser are you using?

Chrome (Version 99.0.4844.83 (Official Build) (x86_64))

What operating system are you using?

macOS Big Sur (11.6.5)

Reproduction URL

Tailwind Play

Describe your issue

Current behavior

This issue applies to projects using the class darkMode strategy.

When using the dark modifier on an custom utility on a HTML element, and that custom utility also contains a dark modifier, you get the following error message:

<css input>: You cannot `@apply` the `dark:bg-green-700` utility here because it creates a circular dependency.

Example:

Custom utility:

.a {
    dark:text-sm
}

HTML

<div class="dark:a">Error</div>

This only applies to the dark modifier.

Expected behavior

  1. This shouldn't produce an error - it should behave like other modifiers and avoid creating a circular dependency
  2. The error message should point at the usage of the custom utility rather that the implementation of the utility itself. Most likely the problem isn't with the utility but with the usage.

simonhmadsen avatar Mar 29 '22 06:03 simonhmadsen

Will have to dig in to this one to really understand the expected behavior because I think there's a chance this really is a circular dependency, but I have to ask first what output you actually expect in this case?

Given this input for example:

.example {
  @apply bg-white dark:bg-black;
}

...this is the generated output:

.example {
  background-color: #fff;
}
.dark .example {
  background-color: #000;
}

So what CSS should dark:example be generating? In my mind, the "correct" output based on how @apply is intended to work is this:

.dark .dark\:example {
  background-color: #fff;
}
.dark .dark .dark\:example {
  background-color: #000;
}

...but that just kinda seems like nonsense. You can see why it might trigger a circular dependency though, by trying to stack dark on top of dark.

adamwathan avatar Mar 29 '22 13:03 adamwathan

Well i think that depends on the direction you want to go - one way would be to merge all declarations of the dark:modifier as it (probably) never makes sense to have duplicate modifiers.

I haven't looked into the implementation, so i don't know how large of a task that would be, or if it is even possible.

Another way of going about this would be to change the error message. The error suggests changing the utility - i would believe it to be more logical to change the use of the utility. So instead of indicating that the @apply function creates a circular dependency, maybe indicate that the use of the custom utility in the HTML creates a circular dependency.

I just tried out what the generated code looks like if you use a different modifier, and it actually isn't much better:

.test {
    @apply focus:bg-black;
}

output:

.focus\:test:focus:focus {
  background-color: #000;
}

Maybe it would make sense to look into merging duplicate modifiers?

simonhmadsen avatar Mar 29 '22 19:03 simonhmadsen

I see that your decorator doesn't actually modify the constructor, it merely runs some side-effect code to add some metadata entry. Thus the @decorator syntax isn't a must. My Indigo Card

Farr69 avatar May 03 '22 05:05 Farr69

I've seen this too lately. I'm pretty sure it was the same problem, i.e. where I had this defined:

.text-subtle {
   @apply text-gray-400 dark:text-gray-600;
}

...that was used like this: <div class="dark:text-subtle"... by someone somewhere in the code base.

From the error message though it sounded like this was a mistake in the config file or in main.css, but where in fact the problem was in code base.

It took me a while to figure out what was going on. So I concur with the last reply, that there seems to be two separate issues here. The first is if this really is a circular dependency? But to me, the more pressing issue is to change the error message to a warning that points the user in the right direction, i.e. that the problem is how a custom class is used in thre code where it creates a circular dependency. Right now the error message doesn't make any sense.

dfallman avatar May 30 '22 23:05 dfallman

Going to close this as a "won't fix" — to me it just doesn't make sense to be trying to add a dark modifier to a class that is already applying the dark modifier internally. Agree in a perfect world we'd come up with a way to detect exactly what the source of the problem is and provide a more instructive error, would happily look at a PR that tried to do that, but just don't have any plans to invest time in that myself.

Doing something like this is just not the intended usage of the framework at all:

.text-subtle {
   @apply text-gray-400 dark:text-gray-600;
}
<div class="dark:text-subtle"...

...so I just can't justify spending my time on it when it's not a problem you should run into when using the framework as intended. I think this type of usage is going to throw an error in v4 with some of the more strict @apply behavior we are working on:

https://github.com/tailwindlabs/tailwindcss/blob/ffef5e2c44252282cc7230287cfb402679f3fd8d/tests/apply.test.js#L1811-L1966

adamwathan avatar Aug 20 '22 13:08 adamwathan

I am running in the same error as well but I can't really follow your explaination @adamwathan. While it is true, that the rules of the framework have to be followed in order to get it to work correctly, users always find a way to modify things for themselves.

For me it was pretty intuitively to create custom classes that do have the appropriate dark mode as well.

  .text-primary{
    @apply text-neutral-800 dark:text-neutral-50;
  }

feels right to me. I understand the way tailwind is handling this but how would you recommend implementing regarding custom classes? That issue breaks the whole apply method for me since I now have to rewrite everything like

  .text-primary{
    @apply text-neutral-800;
  }

.dark-text-primary{
    @apply text-neutral-50;
  }
<div class="text-primary dark:dark-textprimary"></div>

which is anything but convenient.

PLUS: The initial issue does NOT occur for me when removing the manual dark mode class config from tailwind.config.js (darkMode: 'class',).

Edit: Just checked on an older project of mine which has the same strategy implemented

  .input-field {
    @apply light classes here ... dark:placeholder:text-zinc-400 dark:text-white dark:focus:bg-zinc-800;
  }

Which result into this when applying the dark class to the html tag

.dark .input-field {
    --tw-bg-opacity: 1;
    --tw-text-opacity: 1;
    background-color: rgb(63 63 70/var(--tw-bg-opacity));
    color: rgb(255 255 255/var(--tw-text-opacity));
}

This is expected behavior. Why does it not work anymore?

gabrielboeker avatar Sep 13 '22 19:09 gabrielboeker

.text-primary{ @apply text-neutral-800 dark:text-neutral-50; }

Hi @gabrielboeker, this is the strategy we're using as well and is working great for us. I'm actually not sure why this would not be the "intended use" of the framework (?) That seems strange, as this strategy reduces HTML markup clutter significantly! As an example, we use:

<button role="button" class="btn-normal" ...>Click me!</button>

Where btn-normal is defined as:

btn-normal {
   @apply bg-white dark:bg-gray-900 border-gray-300 dark:border-gray-700 text-gray-500 dark:text-gray-600 px-2 py-1 rounded cursor-pointer border shadow-sm ... ;
}

...and even if this makes using of Tailwind a little more like using traditional CSS there's no getting around that this saves an unbelievable amount of markup in the code base, plus that you only have to change it in one place and it changes everywhere, and if you need a one-off special case you can always override something in `btn-normal`` such as:

...class="!bg-yellow-200 dark:!btn-bg-yellow-800/50 btn-normal"

In practice you don't need the ! here if you put the new color before btn-normal but it's a useful visual indicator that you're overriding something.

The problem reported above is actually NOT a problem with using Tailwind this way @adamwathan. It is when you erroneously try to use btn-normal (in this case) like this: dark:btn-normal.

This then creates a circular dependency as you can't apply dark: to dark:, as in dark:dark:bg-gray-900. Fair enough. But that's not to say the strategy itself is wrong.

As I wrote earlier in this thread, I do however believe the postprocessor should give a different/better error message or at least warn the user about the possibility of using dark:dark: somewhere and, if possible, what file/row that error occured.

dfallman avatar Sep 13 '22 22:09 dfallman

Hey @dfallman thanks for your reply. since i was already using that strategy before, it was fairly odd to me that it did not work until i found the issue.

the problem indeed occurs when you (in my case accidentally) assign the dark modifier on a custom class. Somewhere in my code i had a dark:text-primary 😅

I can imagine that more people will do this mistake so I consider that as the solution. Thanks!

gabrielboeker avatar Sep 14 '22 06:09 gabrielboeker