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] addComponents is adding styles to @layer utilities instead of @layer components #15045

Open
saadeghi opened this issue Nov 19, 2024 · 14 comments
Labels

Comments

@saadeghi
Copy link

What version of Tailwind CSS are you using?

v4.0.0-alpha.34

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

@tailwindcss/cli

Reproduction URL

https://github.com/saadeghi/tw4-component-layer-issue

Describe your issue

These are the layers in output CSS file:

@layer theme, base, components, utilities;

Expectation
It's expected for addComponents to add styles to @layer components

Current behavior
Currently addComponents adds styles to @layer utilities, similar to addUtilities

Plugin example:
https://github.com/saadeghi/tw4-component-layer-issue/blob/master/myplugin.js

Generated style:
https://github.com/saadeghi/tw4-component-layer-issue/blob/9b7a944690a35d55c7406756e30cc98c7a239623/output.css#L516

@RobinMalfait
Copy link
Member

Hey!

In v4 this is done on purpose. The biggest benefit of the split in v3 is that you can always override a component with more specific utilities. However, in v4 we improved sorting of all the utilities, which means that this rule should still apply but it reduces the amount of concepts in the system.

Can you share some of the issues you are seeing because of this?

@saadeghi
Copy link
Author

Thanks for the quick answer.

Sure.
The issue is order of styles which is important because order is important in CSS.
I updated the example: https://github.com/saadeghi/tw4-component-layer-issue

For example here I have 2 plugins:
https://github.com/saadeghi/tw4-component-layer-issue/blob/master/input.css

addComponents adds both of them to @layer utilities:
https://github.com/saadeghi/tw4-component-layer-issue/blob/064182b7d683bde9c9bfb9c618a334ed33136202/output.css#L512

But I have no control over the order:

@layer utilities {
  .bigcard {
    background: green;
    padding: 2rem;
  }
  .p-10 {
    padding: calc(var(--spacing) * 10);
  }
  .card {
    background: yellow;
  }
}

For example why one plugin came before p-10 and the other came after p-10?
How are they being sorted
Changing the order in input.css doesn't change anything.

@saadeghi
Copy link
Author

Scenario
What can I do to make sure .bigcard comes after .card? because I want it to modify the default .card style so it must come after .bigcard?
I tried using addUtilities for .bigcard but it works exactly like addComponents.

Workaround
I know I can use:

  • low specificity styles like :where()
  • CSS variables to modify styles in modifier class names instead of CSS rules

But these solutions increases the CSS size and complexity. So I hope there's a way I can control the styles for component (.card) always come before the modifier styles (.bigcard) and Tailwind utility classes.
Expected order:

/* component */
.card {
  background: yellow;
}
/* modifiers */
.bigcard {
  background: green;
  padding: 2rem;
}
/* utilities */
.p-10 {
  padding: calc(var(--spacing) * 10);
}

So the style for this HTML would be predictable

<div class="card">default</div>
<div class="card bigcard">bigcard style overrides the default</div>
<div class="card bg-red-500">bg-red-500 overrides the default</div>

@RobinMalfait
Copy link
Member

Yep I see, the order inside of your CSS is indeed very important. In v4 how it works is that we sort all the utilities (and components) based on the used properties in the CSS and the amount of properties you used.

The idea is that a component with multiple properties (which is typically the case) will be sorted before the ones with fewer properties. In your case .card has a single property and .bigcard has 2, so you can override the background color of .bigcard with .card.

The order difference between .card and .p-10 is based on the used properties. Since the properties used in those classes don't have any overlap, the order between those classes doesn't matter (note: the output is still deterministic and won't appear in random spots during rebuilds).

It also looks like the .card and .bigcard examples are made up to simplify the reproduction (which I appreciate!). But since sorting is based on the amount of properties and which properties were used, a more real example will help here.

Can you make a reproduction with a real example just to verify that you are still running into these issues?

@saadeghi
Copy link
Author

saadeghi commented Nov 20, 2024

sorting is based on the amount of properties and which properties were used

I see! Thanks for the info.
I have a lot of modifier classes, with less or more properties than the component class, and some override others but the problem was I didn't know why they are ordered this way and I thought putting the components in the @layer components would give me a predictable order.

👍 I think you can close this issue if there's no plan to use @layer components. I will check and modify my components to make sure the modifiers have less styles than the components.

The idea is that a component with multiple properties (which is typically the case) will be sorted before the ones with fewer properties

sorting is based on the amount of properties and which properties were used

Can you confirm If I understood the logic correctly:
If a class name has a property that overlaps with properties from another class name, the one with more properties comes first?

@vnphanquang
Copy link

vnphanquang commented Nov 21, 2024

@saadeghi I ran into similar problems while migrating to v4 and decided to use this pattern:

@layer utilities.components.base, utilities.components.modifier;

@utility btn {
  @layer components.base {
    /* base css for btn */
  }
}

@utility btn--outlined {
  @layer components.modifier {
    /* specific css for outlined */
  }
}

So for example, for this markup...

<button class="btn btn--outlined bg-blue-500">...</button>

...specificity is correctly observed: btn < btn--outlined < bg-blue-500, regardless of the ordering in output.

Verbose? Yes, but works well for my use cases. Similarly in JS syntax:

api.addUtilities({
  '.btn': {
    '@layer components.base': {
      // ...
    },
  },
})

Hope that's helpful.

@zoltanszogyenyi
Copy link

Related to this, I would add a new function that adds styles outside the TW layer structure:

#15074

Something like:

addCSS()

@stuyam
Copy link

stuyam commented Nov 22, 2024

Similar issues, when using the tailwind-forms plugin, they are now added to the utilities section, which means I also needed to move my form additions to the utilities layer now instead of the components layer that it was in in v3.

@layer utilities {
  /* Forms */
  .form-input,
  .form-textarea,
  .form-select,
  .form-multiselect,
  .form-checkbox,
  .form-radio{
    @apply block w-full rounded border-0 py-1.5 cursor-pointer
    text-gray-900 shadow-xs ring ring-inset ring-gray-200
    placeholder:text-gray-400 focus:ring-2 focus:ring-inset
    focus:ring-indigo-600 sm:text-sm sm:leading-6;
  }

The issue here is now if I want to do a rounded-r-none if I want to make no rounded corners on the right side to butt up against a button for example, I need to make some (but not all, seemingly random based on order) of the utils important like !rounded-r-none.

It feels like the form plugin would want to be added in the components layer, since they apply a lot of styles that you want to control and be able to override on top of inline.

Here is a working example of the issue: https://play.tailwindcss.com/kuXFgLDpPH

@goofylito
Copy link

Does anyone know a workaround for this issue?

Here’s my specific use case:

In Tailwind v3, I used a plugin to add custom classes to the components layer like this:

const plugin = require("tailwindcss/plugin");

module.exports = plugin(function ({ addComponents }) {
  addComponents({
    ".card": {
      background: "green",
    },
  });
});

This allowed me to use the .card class in markup and override its background color with a utility class:

function App() {
  return <div className="card bg-red-100">card content</div>;
}

The bg-red-100 utility would take precedence over the .card background.

However, after upgrading to Tailwind v4, addComponents now places classes in the utilities layer, making it impossible to override them with utilities like bg-red-100.

Is there any way to force addComponents to add classes to the components layer in v4?

@vnphanquang
Copy link

vnphanquang commented Jan 24, 2025

@goofylito you can have a look at my comment above #15045 (comment) for a possible workaround at the moment.

I'd say having the ability to add directly to the components layer, however, is indeed much more convenient.

@PruthviPraj00
Copy link

Hello @RobinMalfait,

I'm experiencing the same issue with CSS generation for component classes and overriding with Tailwind utilities.

Will this be addressed, or will it remain as is?

@Predota
Copy link

Predota commented Feb 19, 2025

I’m also running into issues because of this change. It was much easier to manage custom styles when addComponents placed them in @layer components. Now, having to put everything in @layer utilities creates conflicts with Tailwind’s utility classes.

@gustavopch
Copy link

gustavopch commented Feb 19, 2025

I believe reducing the number of concepts is beneficial, which is the rationale for this change. However, IMO, there should be another way to achieve what @layer components was intended to do. It existed for a reason.

In my project, I ended up creating an or: variant, which is basically what @adamwathan demonstrated here: https://x.com/adamwathan/status/1818041514863608141

@custom-variant or {
  @layer components {
    @slot;
  }
}

Although addComponents() is now an alias of addUtilities(), the components layer is still declared by Tailwind, so I leveraged it instead of creating a new layer.

This way, if the component has or:text-red-500 but receives text-blue-500 from the props, the latter wins.

Fun (actually sad) fact: I used or: because it takes less space than default:. My initial attempt was ?:, to resemble the nullish coalescing operator. I managed to patch IS_VALID_VARIANT_NAME with a Shell script (since I couldn't use patch-package because Tailwind is sadly distributed in a minified format). However, I later discovered that Oxide, which is a binary and can't be patched, doesn't even consider tokens starting with ? as valid candidates.

@markchitty
Copy link

markchitty commented Feb 26, 2025

Love this @gustavopch. I've wasted hours trying to override select parts of tailwindcss-typography without resorting to !important rules everywhere, but given up. This solves it.

Problem

  • You want the tailwind-typography goodness in your site so you import the plugin
  • The base styles aren't quite right so you add a few global override rules
  • You encounter a specific element where your override styles aren't suitable (but they're perfect everywhere else)
  • 💥 Boom. To override your override you have to resort to using !important classes 😭

Solution

Create @gustavopch's cool variant which injects styles into their own (child) layer.

@custom-variant typo {     /* <== variant prefix   */
  @layer typography {      /* <== child layer name */
    @slot;
  }
}

Use that as a prefix for the tailwind/typography .prose classes wherever you use them (e.g. on a blog post template).

<main class="typo:prose typo:prose-slate typo:dark:prose-invert typo:lg:prose-lg">
  ...
</main>

Tailwind will compile the typography styles into the child layer utilities.typography, which you can then use to target overrides.

@layer utilities.typography {
  .typo\:prose {
    line-height: var(--leading-normal);
    max-width: none;

    h1, h2, h3, h4 {
      font-weight: 500;
      margin-bottom: 0;
    }
    ...
  }
}

Because the utilities.typography child layer has lower specificity than its parent utilities layer, the normal tailwind utility classes take precedence and no ! important shenanigans is needed. \o/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests