tailwindlabs / tailwindcss

A utility-first CSS framework for rapid UI development.
https://tailwindcss.com/
MIT License
83.37k stars 4.22k forks source link

Support complex `addUtilities()` configs #15029

Closed philipp-spiess closed 6 days ago

philipp-spiess commented 1 week ago

This PR adds support for complex addUtilities() configuration objects that use child combinators and other features.

For example, in v3 it was possible to add a utility that changes the behavior of all children of the utility class node by doing something like this:

addUtilities({
  '.red-children > *': {
    color: 'red',
  },
});

This is a pattern that was used by first-party plugins like @tailwindcss/aspect-ratio but that we never made working in v4, since it requires parsing the selector and properly extracting all utility candidates.

While working on the codemod that can transform @layer utilities scoped declarations like the above, we found out a pretty neat heuristics on how to migrate these cases. We're basically finding all class selectors and replace them with &. Then we create a nested CSS structure like this:

.red-children {
  & > * {
    color: red;
  }
}

Due to first party support for nesting, this works as expected in v4.

Test Plan

We added unit tests to ensure the rewriting works in some edge cases. Furthermore we added an integration test running the @tailwindcss/aspect-ratio plugin. We've also installed the tarballs in the Remix example from the playgrounds and ensure we can use the @tailwindcss/aspect-ratio plugin just like we could in v3:

Screenshot 2024-11-18 at 13 44 52
davidmatter commented 3 days ago

@philipp-spiess Am I right to assume that in v4, we need to migrate from

addUtilities({
  "@media (min-width: 768px)": {
    ".article-display-m": {
      "font-size": 60px;
    }
  }
})

to:

addUtilities({
  ".article-display-m": {
    "@media (min-width: 768px)": {
      "font-size": 60px;
    }
  }
})
philipp-spiess commented 3 days ago

@davidmatter Huh this is... interesting! Yes if you can migrate to that that would be easiest. Is this a plugin doing or how did you discover this?

davidmatter commented 3 days ago

We're quite a large company with an extensive CSS-based design system with hundreds of (for example) typography definitions. Now, to get proper intellisense in our code editors and to integrate better with our tailwind projects, we've come up with a custom plugin that parses some of our foundational CSS utilities and adds the definitions to tailwind using addUtilities.

e.g.

  const root = parse(css)
  const jss = objectify(root)
  addUtilities(jss)

It works quite well actually in v3 :) In a perfect world, these definitions would of course be extracted to their own components to support the official tailwind way of doing things etc. but that would be A LOT of work.

davidmatter commented 3 days ago

For parsing and objectify, we use https://github.com/postcss/postcss-js

philipp-spiess commented 3 days ago

I see, so you're not expecting this to generate the right CSS anyways and only use this to extend the type hints for intellisense? I think it should be possible to write a quick function that inverts the object properties in this case?

davidmatter commented 16 hours ago

@philipp-spiess Thanks for taking the time. Well, we actually use it to generate prunable CSS. TBH I think it's the best pattern to work with legacy CSS code because of 1) Intellisense and 2) Resulting CSS can be pruned by tailwind and runs through the official plugin system.

Inverting the objects is alright for us. I suspect that we're not the only ones using this pattern, though. Might be advisable to mark this as a breaking change to save everyone's time :)

davidmatter commented 16 hours ago

BTW: Good to see an Austrian engineer being part of tailwind! Keep it up