21 Commits

Author SHA1 Message Date
Philipp Spiess
468cb5e99e
Detect and migrate static plugin usages (#14648)
This PR builds on top of the new [JS config to CSS
migration](https://github.com/tailwindlabs/tailwindcss/pull/14651) and
extends it to support migrating _static_ plugins.

What are _static_ plugins you might ask? Static plugins are plugins
where we can statically determine that these are coming from a different
file (so there is nothing inside the JS config that creates them). An
example for this is this config file:

```js
import typographyPlugin from '@tailwindcss/typography'
import { type Config } from 'tailwindcss'

export default {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  darkMode: 'selector',
  plugins: [typographyPlugin],
} satisfies Config
```

Here, the `plugins` array only has one element and it is a static import
from the `@tailwindcss/typography` module. In this PR we attempt to
parse the config file via Tree-sitter to extract the following
information from this file:

- What are the contents of the `plugins` array
- What are statically imported resources from the file

We then check if _all_ entries in the `plugins` array are either static
resources or _strings_ (something I saw working in some tests but I’m
not sure it still does). We migrate the JS config file to CSS if all
plugins are static and we can migrate them to CSS `@plugin` calls.

## Todo

This will need to be rebased after the updated tests in #14648
2024-10-14 17:45:36 +02:00
Philipp Spiess
4b19de3a45
Address follow-up work for #14639 (#14650)
This PR adds a few more test cases to #14639 and updates the
documentation.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-10-14 14:33:14 +00:00
Philipp Spiess
0cfb98484b
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.

The example we use in the integration test is a config that looks like
this:

```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'

module.exports = {
  darkMode: 'selector',
  content: ['./src/**/*.{html,js}'],
  theme: {
    boxShadow: {
      sm: '0 2px 6px rgb(15 23 42 / 0.08)',
    },
    colors: {
      red: {
        500: '#ef4444',
      },
    },
    fontSize: {
      xs: ['0.75rem', { lineHeight: '1rem' }],
      sm: ['0.875rem', { lineHeight: '1.5rem' }],
      base: ['1rem', { lineHeight: '2rem' }],
    },
    extend: {
      colors: {
        red: {
          600: '#dc2626',
        },
      },
      fontFamily: {
        sans: 'Inter, system-ui, sans-serif',
        display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
      },
      borderRadius: {
        '4xl': '2rem',
      },
    },
  },
  plugins: [],
} satisfies Config
```

As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.

In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:

```css
@import 'tailwindcss';

@source './**/*.{html,js}';

@variant dark (&:where(.dark, .dark *));

@theme {
  --box-shadow-*: initial;
  --box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);

  --color-*: initial;
  --color-red-500: #ef4444;

  --font-size-*: initial;
  --font-size-xs: 0.75rem;
  --font-size-xs--line-height: 1rem;
  --font-size-sm: 0.875rem;
  --font-size-sm--line-height: 1.5rem;
  --font-size-base: 1rem;
  --font-size-base--line-height: 2rem;

  --color-red-600: #dc2626;

  --font-family-sans: Inter, system-ui, sans-serif;
  --font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";

  --border-radius-4xl: 2rem;
} 
```

This replicates all features of the JS config so we can even delete the
existing JS config in this case.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
Robin Malfait
a3812942ac
Use consistent quotes (#14640)
Small improvement, we noticed that some quotes were not consistent with
others. Let's make them consistent!
2024-10-10 14:29:36 +00:00
Robin Malfait
fb1731a2de
Inject @config "..." when a tailwind.config.{js,ts,...} is detected (#14635)
This PR injects a `@config "…"` in the CSS file if a JS based config has
been found.

We will try to inject the `@config` in a sensible place:
1. Above the very first `@theme`
2. If that doesn't work, below the last `@import`
3. If that doesn't work, at the top of the file (as a last resort)
2024-10-10 14:02:42 +00:00
Jordan Pittman
4d1becd2f9
Migrate utilities in CSS files imported into layers (#14617)
When a stylesheet is imported with `@import “…” layer(utilities)` that
means that all classes in that stylesheet and any of its imported
stylesheets become candidates for `@utility` conversion.

Doing this correctly requires us to place `@utility` rules into separate
stylesheets (usually) and replicate the import tree without layers as
`@utility` MUST be root-level. If a file consists of only utilities we
won't create a separate file for it and instead place the `@utility`
rules in the same stylesheet.

Been doing a LOT of pairing with @RobinMalfait on this one but I think
this is finally ready to be looked at

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-10 15:44:04 +02:00
Robin Malfait
3ae22f1f9d
Migrate @media screen(…) (#14603)
This PR adds a codemod that migrates the `@media screen(…)` to the
properly expanded `@media (…)` syntax.

```css
@media screen(md) {
  .foo {
    color: red;
  }
}
```

Will be converted to:
```css
@media (width >= 48rem) {
  .foo {
    color: red;
  }
}
```

If you happen to have custom screens (even complex ones), the screen
will be converted to a custom media query.

```css
@media screen(foo) {
  .foo {
    color: red;
  }
}
```
With a custom `tailwind.config.js` file with a config like this:
```js
module.exports = {
  // …
  theme: {
    screens: {
      foo: { min: '100px', max: '123px' },
    },
  }
}
```

Then the codemod will convert it to:
```css
@media (123px >= width >= 100px) {
  .foo {
    color: red;
  }
}
```
2024-10-10 15:06:53 +02:00
Philipp Spiess
7be5346e2e
Ensure upgrade tool has access to a JS config (#14597)
In order to properly migrate your Tailwind CSS v3 project to v4, we need
access to the JavaScript configuration object. This was previously only
required for template migrations, but in this PR we're making it so that
this is also a prerequisite of the CSS migrations. This is because some
migrations, like `@apply`, also need to convert candidates that to the
v4 syntax and we need the full config in order to properly validate
them.

In addition to requiring a JS config, we also now attempt to
automatically find the right configuration file inside the current
working directory. This is now matching the behavior of the Tailwind CSS
v3 CLI where it will find the config automatically if it's in the
current directory and called `tailwind.conf.js`.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-07 18:02:28 +02:00
Robin Malfait
e3764ac843
Ensure CSS before a layer stays unlayered (#14596)
This PR fixes an issue where CSS that existed before a layer:

```css
.foo {
  color: red;
}

@layer components {
  .bar {
    color: blue;
  }
}
```

Was turned into an `@layer` without a name:
```css
@layer {
  .foo {
    color: red;
  }
}

@utility bar {
  color: blue;
}
```

But in this case, it should stay as-is:
```css
.foo {
  color: red;
}

@utility bar {
  color: blue;
}
```
2024-10-04 12:47:05 +02:00
Philipp Spiess
2e87288526
Migrate at-apply utilites with template migrations (#14574)
This PR extracts all _candidate migrations_ from the existing _template
migrations_ and reuses these in the `@apply` CSS migration. Seems like
this _JustWorks_.

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-10-03 09:35:28 -04:00
Philipp Spiess
65240c9240
Template migrations: Migrate v3 prefixes to v4 (#14557)
This PR adds a new migration that can migrate Tailwind CSS v3 style
prefixes into Tailwind CSS v4.

The migration is split into three separate pieces of work:

1. Firstly, we need to read the full JavaScript config to get the _old_
prefix option. This is necessary because in v4, we will not allow things
like custom-separators for the prefix. From this option we will then try
and compute a new prefix (in 90% of the cases this is going to just
remove the trailing `-` but it can also work in more complex cases).
2. Then we migrate all Candidates. The important thing here is that we
need to operate on the raw candidate string because by relying on
`parseCandidate` (which we do for all other migrations) would not work,
as the candidates are not valid in v4 syntax. More on that in a bit.
3. Lastly we also make sure to update the CSS config to include the new
prefix. This is done by prepending the prefix option like so:
    
    ```css
    @import "tailwindcss" prefix(tw);
    ```

### Migrating candidates

The main difference between v3 prefixes and v4 prefixes is that in v3,
the prefix was _part of the utility_ where as in v4 it is _always in
front of the CSS class.

So, for example, this candidate in v3: 

```
hover:-tw-mr-4
```

Would be converted to the following in v4:

```
tw:hover:-mr-4
```

Since the first example _won't parse as a valid Candidate in v4, as the
`tw-mr` utility does not exist, we have to operate on the raw candidate
string first. To do this I created a fork of the `parseCandidate`
function _without any validation of utilities or variants_. This is used
to identify part of the candidate that is the `base` and then ensuring
the `base` starts with the old prefix. We then remove this to create an
"unprefixed" candidate that we validate against a version of the
DesignSystem _with no prefixes configured_. If the variant is valid this
way, we can then print it again with the `DesignSystem` that has the new
prefix to get the migrated version.

Since we set up the `DesignSystem` to include the new prefix, we can
also be certain that migrations that happen afterwards would still
disqualify candidates that aren't valid according to the new prefix
policy. This does mean we need to have the prefix fixup be the first
step in our pipeline.

One interesting bit is that in v3, arbitrary properties did not require
prefixes where as in v4 they do. So the following candidate:

```
[color:red]
```

Will be converted to:

```
tw:[color:red]
```
2024-10-01 18:04:08 +02:00
Robin Malfait
4f8ca556cf
CSS codemod: inject @import in a more expected location (#14536)
This PR inserts the `@import` in a more sensible location when running
codemods.

The idea is that we replace `@tailwind base; @tailwind components;
@tailwind utilities;` with the much simple `@import "tailwindcss";`. We
did this by adding the `@import` to the top of the file.

While this is correct, this means that the diff might not be as clear.
For example, if you have a situation where you have a license comment:
```css
/**! My license comment */
@tailwind base;
@tailwind components;
@tailwind utilities;
```

This resulted in:
```css
@import "tailwindcss";
/**! My license comment */
```

While it is not wrong, it feels weird that this behaves like this. In
this commit we make sure that it is injected in-place (the first
`@tailwind` at-rule we find) and fixup the position if we can't inject
it in-place.

The above example results in this:
```css
/**! My license comment */
@import "tailwindcss";
```

However, there are scenario's where you can't replace the `@tailwind`
directives directly. E.g.:
```css
/**! My license comment */
html {
  color: red;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
```

If we replace the `@tailwind` directives in-place, it would look like
this:
```css
/**! My license comment */
html {
  color: red;
}
@import "tailwindcss";
```

But this is invalid CSS, because you can't have CSS above an `@import`
at-rule. There are some exceptions like:
- `@charset`
- `@import`
- `@layer foo, bar;` (just the order, without a body)
- comments

In this scenario, we inject the import in the nearest place where it is
allowed to. In this case:

```css
/**! My license comment */
@import "tailwindcss";
@layer base {
  html {
     color: red;
  }
}
```

Additionally, we will wrap the existing CSS in an `@layer` of the first
Tailwind directive we saw. In this case an `@layer base`. This ensures
that utilities still win from the default styles.

Also note that the (license) comment is allowed to exist before the
`@import`, therefore we do not put the `@import` above it. This also
means that the diff doesn't touch the license header at all, which makes
the diffs cleaner and easier to reason about.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-09-30 13:32:30 +00:00
Robin Malfait
8bbdb57457
CSS codemod: ensure we don't lose selectors (#14518)
This PR fixes an issue where a CSS rule with a selector that contains
multiple selectors lost everything but the last selector.

While testing the `npx @tailwindcss/upgrade` codemods on real world
projects, I noticed that we lost one of the selectors in the
`docker/docs` repository.

```diff
diff --git a/assets/css/toc.css b/assets/css/toc.css
index 91ff92d7cd..3b2432e913 100644
--- a/assets/css/toc.css
+++ b/assets/css/toc.css
@@ -2,7 +2,7 @@
   #TableOfContents {
     .toc a {
       @apply block max-w-full truncate py-1 pl-2 hover:font-medium hover:no-underline;
-      &[aria-current="true"],
+      
       &:hover {
         @apply border-l-2 border-l-gray-light bg-gradient-to-r from-gray-light-100 font-medium text-black dark:border-l-gray-dark dark:from-gray-dark-200 dark:text-white;
       }
```

This PR fixes the issue by not overriding the `node.selector` internally
with the last selector we handled. Instead, we let the selector parser
handle it entirely.
2024-09-25 18:39:00 +02:00
Robin Malfait
ceae1dbdfb
CSS codemod: do not wrap comment nodes in @layer (#14517)
When CSS exists between two tailwind directives, then the CSS will be
wrapped in an `@layer` to ensure it all ends up in the correct location
in the final CSS file.

However, if the only thing between two tailwind directives is a comment,
then the comment will also be wrapped in an `@layer` directive.

E.g.:
```css
@tailwind base;
/* This is a comment */
@tailwind components;
/* This is another comment */
@tailwind utilities;
```

Results in:
```css
@import "tailwindcss";
@layer base {
  /* This is a comment */
}
@layer components {
  /* This is another comment */
}
```

In this case, the layers don't matter, so this can be simplified to:
```css
@import "tailwindcss";
/* This is a comment */
/* This is another comment */
```
2024-09-25 12:58:05 +00:00
Robin Malfait
e770c0da30
CSS codemod: Migrate @import "tailwindcss/tailwind.css" to @import "tailwindcss" (#14514)
This PR adds an additional step to ensure we migrate:
```css
@import "tailwindcss/tailwind.css";
```

To:
```css
@import "tailwindcss";
```
2024-09-25 14:48:22 +02:00
Robin Malfait
723f5db38b
CSS codemod: Fix incorrect empty layer() at the end of @import at-rules (#14513)
This PR fixes an issue where some `@import` at-rules had an empty
`layer()` attached at the end of the `@import` string.

We should only add that if a Tailwind directive or Tailwind import such
as `@tailwind base` or `@import "tailwindcss/base"` preceded the current
`@import` at-rule. If there was no Tailwind directive, the `lastLayer`
will be empty and we don't need to attach it to the `@import` string.
2024-09-25 13:48:14 +02:00
Robin Malfait
951f6448fe
Improve missing layer codemod (#14512)
This PR improves the missing layers codemod where everything after the
last Tailwind directive can stay as-is without wrapping it in a `@layer`
directive.

The `@layer` at-rules are only important for CSS that exists between
Tailwind directives.

E.g.:
```css
@tailwind base;

html {}

@tailwind components;

.btn {}

@tailwind utilities;

.foo {}

.bar {}
```

Was transformed into:
```css
@import "tailwindcss";

@layer base {
  html {}
}

@layer components {
  .btn {}
}

@layer utilities {
  .foo {}
  
  .bar {}
}
```

But the last `@layer utilities` is already in the correct spot, so we
can simplify this to just this instead:
```css
@import "tailwindcss";

@layer base {
  html {}
}

@layer components {
  .btn {}
}

.foo {}

.bar {}
```
2024-09-25 08:42:19 +00:00
Robin Malfait
d869442a54
Add CSS codemod for missing @layer (#14504)
This PR adds a codemod that ensures that some parts of your stylesheet
are wrapped in an `@layer`.

This is a follow-up PR of #14411, in that PR we migrate `@tailwind`
directives to imports.

As a quick summary, that will turn this:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Into:
```css
@import 'tailwindcss';
```

But there are a few issues with that _if_ we have additional CSS on the
page. For example let's imagine we had this:
```css
@tailwind base;

body {
  background-color: red;
}

@tailwind components;

.btn {}

@tailwind utilities;
```

This will now be turned into:
```css
@import 'tailwindcss';

body {
  background-color: red;
}

.btn {}
```

But in v4 we use real layers, in v3 we used to replace the directive
with the result of that layer. This means that now the `body` and `.btn`
styles are in the incorrect spot.

To solve this, we have to wrap them in a layer. The `body` should go in
an `@layer base`, and the `.btn` should be in an `@layer components` to
make sure it's in the same spot as it was before.

That's what this PR does, the original input will now be turned into:

```css
@import 'tailwindcss';

@layer base {
  body {
    background-color: red;
  }
}

@layer components {
  .btn {
  }
}
```

There are a few internal refactors going on as well, but those are less
important.
2024-09-24 16:32:50 +00:00
Robin Malfait
d14249ddc2
Add CSS codemods for migrating @layer utilities (#14455)
This PR adds CSS codemods for migrating existing `@layer utilities` to
`@utility` directives.

This PR has the ability to migrate the following cases:

---

The most basic case is when you want to migrate a simple class to a
utility directive.

Input:
```css
@layer utilities {
  .foo {
    color: red;
  }

  .bar {
    color: blue;
  }
}
```

Output:
```css
@utility foo {
  color: red;
}

@utility bar {
  color: blue;
}
```

You'll notice that the class `foo` will be used as the utility name, the
declarations (and the rest of the body of the rule) will become the body
of the `@utility` definition.

---

In v3, every class in a selector will become a utility. To correctly
migrate this to `@utility` directives, we have to register each class in
the selector and generate `n` utilities.

We can use nesting syntax, and replace the current class with `&` to
ensure that the final result behaves the same.

Input:
```css
@layer utilities {
  .foo .bar .baz {
    color: red;
  }
}
```

Output:
```css
@utility foo {
  & .bar .baz {
    color: red;
  }
}

@utility bar {
  .foo & .baz {
    color: red;
  }
}

@utility .baz {
  .foo .bar & {
    color: red;
  }
}
```

In this case, it could be that you know that some of them will never be
used as a utility (e.g.: `hover:bar`), but then you can safely remove
them.

---

Even classes inside of `:has(…)` will become a utility. The only
exception to the rule is that we don't do it for `:not(…)`.

Input:
```css
@layer utilities {
  .foo .bar:not(.qux):has(.baz) {
    display: none;
  }
}
```

Output:
```css
@utility foo {
  & .bar:not(.qux):has(.baz) {
    display: none;
  }
}

@utility bar {
  .foo &:not(.qux):has(.baz) {
    display: none;
  }
}

@utility baz {
  .foo .bar:not(.qux):has(&) {
    display: none;
  }
}
```

Notice that there is no `@utility qux` because it was used inside of
`:not(…)`.

---

When classes are nested inside at-rules, then these classes will also
become utilities. However, the `@utility <name>` will be at the top and
the at-rules will live inside of it. If there are multiple classes
inside a shared at-rule, then the at-rule will be duplicated for each
class.

Let's look at an example to make it more clear:

Input:
```css
@layer utilities {
  @media (min-width: 640px) {
    .foo {
      color: red;
    }

    .bar {
      color: blue;
    }

    @media (min-width: 1024px) {
      .baz {
        color: green;
      }

      @media (min-width: 1280px) {
        .qux {
          color: yellow;
        }
      }
    }
  }
}
```

Output:
```css
@utility foo {
  @media (min-width: 640px) {
    color: red;
  }
}

@utility bar {
  @media (min-width: 640px) {
    color: blue;
  }
}

@utility baz {
  @media (min-width: 640px) {
    @media (min-width: 1024px) {
      color: green;
    }
  }
}

@utility qux {
  @media (min-width: 640px) {
    @media (min-width: 1024px) {
      @media (min-width: 1280px) {
        color: yellow;
      }
    }
  }
}
```

---

When classes result in multiple `@utility` directives with the same
name, then the definitions will be merged together.

Input:
```css
@layer utilities {
  .no-scrollbar::-webkit-scrollbar {
    display: none;
  }

  .no-scrollbar {
    -ms-overflow-style: none;
    scrollbar-width: none;
  }
}
```

Intermediate representation:
```css
@utility no-scrollbar {
  &::-webkit-scrollbar {
    display: none;
  }
}

@utility no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;
}
```

Output:
```css
@utility no-scrollbar {
  &::-webkit-scrollbar {
    display: none;
  }
  -ms-overflow-style: none;
  scrollbar-width: none
}
```

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-24 18:17:09 +02:00
Robin Malfait
67d1849f34
Add CSS codemods for migrating @tailwind directives (#14411)
This PR adds CSS codemods for migrating existing `@tailwind` directives
to the new alternatives.

This PR has the ability to migrate the following cases:

---

Typical default usage of `@tailwind` directives in v3.

Input:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Output:
```css
@import 'tailwindcss';
```

---

Similar as above, but always using `@import` instead of `@import`
directly.

Input:
```css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
```

Output:
```css
@import 'tailwindcss';
```

---

When you are _only_ using `@tailwind base`:

Input:
```css
@tailwind base;
```

Output:
```css
@import 'tailwindcss/theme' layer(theme);
@import 'tailwindcss/preflight' layer(base);
```

---

When you are _only_ using `@tailwind utilities`:

Input:
```css
@tailwind utilities;
```

Output:
```css
@import 'tailwindcss/utilities' layer(utilities);
```

---

If the default order changes (aka, `@tailwind utilities` was defined
_before_ `@tailwind base`), then an additional `@layer` will be added to
the top to re-define the default order.

Input:
```css
@tailwind utilities;
@tailwind base;
```

Output:
```css
@layer theme, components, utilities, base;
@import 'tailwindcss';
```

---

When you are _only_ using `@tailwind base; @tailwind utilities;`:

Input:
```css
@tailwind base;
@tailwind utilities;
```

Output:
```css
@import 'tailwindcss';
```

We currently don't have a concept of `@tailwind components` in v4, so if
you are not using `@tailwind components`, we can expand to the default
`@import 'tailwindcss';` instead of the individual imports.

---

`@tailwind screens` and `@tailwind variants` are not supported/necessary
in v4, so we can safely remove them.

Input:
```css
@tailwind screens;
@tailwind variants;
```

Output:
```css
```
2024-09-18 22:40:23 +02:00
Robin Malfait
ee7e02b1f3
Add initial codemod tooling (#14434)
This PR adds some initial tooling for codemods. We are currently only
interested in migrating CSS files, so we will be using PostCSS under the
hood to do this. This PR also implements the "migrate `@apply`" codemod
from #14412.

The usage will look like this:

```sh
npx @tailwindcss/upgrade
```

You can pass in CSS files to transform as arguments:

```sh
npx @tailwindcss/upgrade src/**/*.css
```

But, if none are provided, it will search for CSS files in the current
directory and its subdirectories.

```
≈ tailwindcss v4.0.0-alpha.24

│ No files provided. Searching for CSS files in the current
│ directory and its subdirectories…

│ Migration complete. Verify the changes and commit them to
│ your repository.
```

The tooling also requires the Git repository to be in a clean state.
This is a common convention to ensure that everything is undo-able. If
we detect that the git repository is dirty, we will abort the migration.

```
≈ tailwindcss v4.0.0-alpha.24

│ Git directory is not clean. Please stash or commit your
│ changes before migrating.

│ You may use the `--force` flag to override this safety
│ check.
```


---

This PR alsoo adds CSS codemods for migrating existing `@apply`
directives to the new version.

This PR has the ability to migrate the following cases:

---

In v4, the convention is to put the important modifier `!` at the end of
the utility class instead of right before it. This makes it easier to
reason about, especially when you are variants.

Input:
```css
.foo {
  @apply !flex flex-col! hover:!items-start items-center;
}
```

Output:
```css
.foo {
  @apply flex! flex-col! hover:items-start! items-center;
}
```


---

In v4 we don't support `!important` as a marker at the end of `@apply`
directives. Instead, you can append the `!` to each utility class to
make it `!important`.

Input:
```css
.foo {
  @apply flex flex-col !important;
}
```

Output:
```css
.foo {
  @apply flex! flex-col!;
}
```
2024-09-18 16:45:43 +02:00