When I added source maps to PostCSS I mistakenly assumed that `.source`
on a node could be `undefined`. The comment above the property in
PostCSS says that `source` can be `undefined` but this is a commentary
on the value upon **access** not its expected value on **write**:
```ts
declare abstract class Node_ {
/**
* …
*
* The nodes that are created manually using the public APIs
* provided by PostCSS will have `source` undefined and
* will be absent in the source map.
*
* …
*/
source?: Node.Source
}
```
Rather, what these types mean is that *if the property exists* it must
be defined. But otherwise the property can be missing if a node has no
source location metadata. This generally wasn't a problem with the
string-returning APIs but the `toJSON()` API in PostCSS expects that
`source` is defined if present. This caused a crash because our license
comment doesn't have a source location.
I've addressed this by deleting the `source` property from the node if
source location data is not available.
Fixes#18082
ref https://github.com/parcel-bundler/parcel/issues/10161
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.
### What changed?
#### ✳️ dedent (1.5.3 → 1.6.0) · [Repo](https://github.com/dmnd/dedent)
· [Changelog](https://github.com/dmnd/dedent/blob/main/CHANGELOG.md)
<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/dmnd/dedent/releases/tag/v1.6.0">1.6.0</a></h4>
<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>feat: add <code class="notranslate">trimWhitespace</code> option by
<a href="https://bounce.depfu.com/github.com/43081j">@43081j</a> in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/97">#97</a>
</li>
</ul>
<h2 dir="auto">New Contributors</h2>
<ul dir="auto">
<li>
<a href="https://bounce.depfu.com/github.com/43081j">@43081j</a> made
their first contribution in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/97">#97</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/compare/v1.5.3...v1.6.0"><tt>v1.5.3...v1.6.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="https://depfu.com/packages/npm/dedent/feedback">Please let us
know.</a></em></p>
</details>
<details>
<summary>Commits</summary>
<p><a
href="90644fe0be...ab2ce25762">See
the full diff on Github</a>. The new version differs by 2 commits:</p>
<ul>
<li><a
href="ab2ce25762"><code>1.6.0
(#98)</code></a></li>
<li><a
href="86902f7c97"><code>feat:
add `trimWhitespace` option (#97)</code></a></li>
</ul>
</details>
---

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.
<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Closes#13694Closes#13591
# Source Maps Support for Tailwind CSS
This PR adds support for source maps to Tailwind CSS v4 allowing us to
track where styles come from whether that be user CSS, imported
stylesheets, or generated utilities. This will improve debuggability in
browser dev tools and gives us a good foundation for producing better
error messages. I'll go over the details on how end users can enable
source maps, any limitations in our implementation, changes to the
internal `compile(…)` API, and some details and reasoning around the
implementation we chose.
## Usage
### CLI
Source maps can be enabled in the CLI by using the command line argument
`--map` which will generate an inline source map comment at the bottom
of your CSS. A separate file may be generated by passing a file name to
`--map`:
```bash
# Generates an inline source map
npx tailwindcss -i input.css -o output.css --map
# Generates a separate source map file
npx tailwindcss -i input.css -o output.css --map output.css.map
```
### PostCSS
Source maps are supported when using Tailwind as a PostCSS plugin *in
development mode only*. They may or may not be enabled by default
depending on your build tool. If they are not you may be able to
configure them within your PostCSS config:
```jsonc
// package.json
{
// …
"postcss": {
"map": { "inline": true },
"plugins": {
"@tailwindcss/postcss": {},
},
}
}
```
### Vite
Source maps are supported when using the Tailwind CSS Vite plugin in
*development mode only* by enabling the `css.devSourcemap` setting:
```js
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tailwindcss()],
css: {
devSourcemap: true,
},
})
```
Now when a CSS file is requested by the browser it'll have an inline
source map comment that the browser can use.
## Limitations
- Production build source maps are currently disabled due to a bug in
Lightning CSS. See
https://github.com/parcel-bundler/lightningcss/pull/971 for more
details.
- In Vite, minified CSS build source maps are not supported at all. See
https://github.com/vitejs/vite/issues/2830 for more details.
- In PostCSS, minified CSS source maps are not supported. This is due to
the complexity required around re-associating every AST node with a
location in the generated, optimized CSS. This complexity would also
have a non-trivial performance impact.
## Testing
Here's how to test the source map functionality in different
environments:
### Testing the CLI
1. Setup typical project that the CLI can use and with sources to scan.
```css
@import "tailwindcss";
@utilty my-custom-utility {
color: red;
}
/* to test `@apply` */
.card {
@apply bg-white text-center shadow-md;
}
```
2. Build with source maps:
```bash
bun /path/to/tailwindcss/packages/@tailwindcss-cli/src/index.ts --input input.css -o output.css --map
```
3. Open Chrome DevTools, inspect an element with utility classes, and
you should see rules pointing to `input.css` or
`node_modules/tailwindcss/index.css`
### Testing with Vite
Testing in Vite will require building and installing necessary files
under `dist/*.tgz`.
1. Create a Vite project and enable source maps in `vite.config.js`:
```js
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tailwindcss()],
css: {
// This line is required for them to work
devSourcemap: true,
},
})
```
2. Add a component that uses Tailwind classes and custom CSS:
```jsx
// ./src/app.jsx
export default function App() {
return (
<div className="bg-blue-500 my-custom-class">
Hello World
</div>
)
}
```
```css
/* ./src/styles.css */
@import "tailwindcss";
@utilty my-custom-utility {
color: red;
}
/* to test `@apply` */
.card {
@apply bg-white text-center shadow-md;
}
```
3. Run `npm run dev`, open DevTools, and inspect elements to verify
source mapping works for both utility classes and custom CSS.
### Testing with PostCSS CLI
1. Create a test file and update your PostCSS config:
```css
/* input.css */
@import "tailwindcss";
@layer components {
.card {
@apply p-6 rounded-lg shadow-lg;
}
}
```
```jsonc
// package.json
{
// …
"postcss": {
"map": {
"inline": true
},
"plugins": {
"/path/to/tailwindcss/packages/packages/@tailwindcss-postcss/src/index.ts": {}
}
}
}
```
2. Run PostCSS through Bun:
```bash
bunx --bun postcss ./src/index.css -o out.css
```
3. Inspect the output CSS - it should include an inline source map
comment at the bottom.
### Testing with PostCSS + Next.js
Testing in Next.js will require building and installing necessary files
under `dist/*.tgz`. However, I've not been able to get CSS source maps
to work in Next.js without this hack:
```js
const nextConfig: NextConfig = {
// next.js overwrites config.devtool so we prevent it from doing so
// please don't actually do this…
webpack: (config) =>
Object.defineProperty(config, "devtool", {
get: () => "inline-source-map",
set: () => {},
}),
};
```
This is definitely not supported and also doesn't work with turbopack.
This can be used to test them temporarily but I suspect that they just
don't work there.
### Manual source map analysis
You can analyze source maps using Evan Wallace's [Source Map
Visualization](https://evanw.github.io/source-map-visualization/) tool
which will help to verify the accuracy and quality of source maps. This
is what I used extensively while developing this implementation.
It'll help verify that custom, user CSS maps back to itself in the
input, that generated utilities all map back to `@tailwind utilities;`,
that source locations from imported files are also handled correctly,
etc… It also highlights the ranges of stuff so it's easy to see if there
are off-by-one errors.
It's easiest to use inline source maps with this tool because you can
take the CSS file and drop it on the page and it'll analyze it while
showing the file content.
If you're using Vite you'll want to access the CSS file with `?direct`
at the end so you don't get a JS module back.
## Implementation
The source map implementation follows the ECMA-426 specification and
includes several key components to aid in that goal:
### Source Location Tracking
Each emittable AST node in the compilation pipeline tracks two types of
source locations:
- `src`: Original source location - [source file, start offset, end
offset]
- `dst`: Generated source location - [output file, start offset, end
offset]
This dual tracking allows us to maintain mappings between the original
source and generated output for things like user CSS, generated
utilities, uses of `@apply`, and tracking theme variables.
It is important to note that source locations for nodes _never overlap_
within a file which helps simplify source map generation. As such each
type of node tracks a specific piece of itself rather than its entire
"block":
| Node | What a `SourceLocation` represents |
| ----------- |
---------------------------------------------------------------- |
| Style Rule | The selector |
| At Rule | Rule name and params, includes the `@` |
| Declaration | Property name and value, excludes the semicolon |
| Comment | The entire comment, includes the start `/*` and end `*/`
markers |
### Windows line endings when parsing CSS
Because our AST tracks nodes through offsets we must ensure that any
mutations to the file do *not* change the lenth of the string. We were
previously replacing `\r\n` with `\n` (see [filter code
points](https://drafts.csswg.org/css-syntax/#css-filter-code-points)
from the spec) — which changes the length of the string and all offsets
may end up incorrect. The CSS parser was updated to handle the CRLF
token directly by skipping over the `\r` and letting remaining code
handle `\n` as it did previously. Some additional tweaks were required
when "peeking" the input but those changes were fairly small.
### Tracking of imports
Source maps need paths to the actual imported stylesheets but the
resolve step for stylesheets happens inside the call to `loadStylesheet`
which make the file path unavailable to us. Because of this the
`loadStylesheet` API was augmented such that it has to return a `path`
property that we can then use to identify imported sources. I've also
made the same change to the `loadModule` API for consistency but nothing
currently uses this property.
The `path` property likely makes `base` redundant but elminating that
(if we even want to) is a future task.
### Optimizing the AST
Our optimization pass may intoduce some nodes, for example, fallbacks we
create for `@property`. These nodes are linked back to `@tailwind
utilities` as ultimately that is what is responsible for creating them.
### Line Offset Tables
A key component to our source map generation is the line offset table,
which was inspired by some ESBuild internals. It stores a sorted list of
offsets for the start of each line allowing us to translate offsets to
line/column `Position`s in `O(log N)` time and from `Position`s to
offsets in `O(1)` time. Creation of the table takes `O(N)` time.
This means that we can store code point offsets for source locations and
not have to worry about computing or tracking line/column numbers during
parsing and serialization. Only when a source map is generated do these
offsets need to be computed. This ensures the performance penalty when
not using source maps is minimal.
### Source Map Generation
The source map returned by `buildSourceMap()` is designed to follow the
[ECMA-426 spec](https://tc39.es/ecma426). Because that spec is not
completely finalized we consider the result of `buildSourceMap()` to be
internal API that may change as the spec chamges.
The produces source map is a "decoded" map such that all sources and
mappings are in an object graph. A library like `source-map-js` must be
used to convert this to an encoded source map of the right version where
mappings are encoded with base 64 VLQs.
Any specific integration (Vite, PostCSS, etc…) can then use
`toSourceMap()` from `@tailwindcss/node` to convert from the internal
source map to an spec-compliant encoded source map that can be
understood by other tools.
### Handling minification in Lightning
Since we use Lightning CSS for optimization, and it takes in an input
map, we generate an encoded source map that we then pass to lightning.
The output source map *from lighting itself* is then passed back in
during the second optimization pass. The final map is then passed from
lightning to the CLI (but not Vite or PostCSS — see the limitations
section for details).
In some cases we have to "fix up" the output CSS. When this happens we
use `magic-string` to do the replacement in a way that is trackable and
`@amppproject/remapping` to map that change back onto the original
source map. Once the need for these fix ups disappear these dependencies
can go away.
Notes:
- The accuracy of source maps run though lightning is reduced as it only
tracks on a per-rule level. This is sufficient enough for browser dev
tools so should be fine.
- Source maps during optimization do not function properly at this time
because of a bug in Lightning CSS regarding license comments. Once this
bug is fixed they will start working as expected.
### How source locations flow through the system
1. During initial CSS parsing, source locations are preserved.
2. During parsing these source locations are also mapped to the
destinations which supports an optimization for when no utilities are
generated.
3. Throughout the compilation process, transformations maintain source
location data
4. Generated utilities are explicitly pointed to `@tailwind utilities`
unless generated by `@apply`.
5. When optimization is enabled, source maps are remapped through
lightningcss
6. Final source maps are written in the requested format (inline or
separate file)
Closes#17295
This commit addresses an issue where the PostCSS plugin would get stuck
in an error state when processing files with e.g. invalid @apply
directives.
This change prevents the PostCSS plugin from getting stuck in an error
states particularly when the error happened inside an `@import`ed CSS
files (as these were not registered as dependencies correctly before).
## Error overlays
Some frameworks (e.g. Angular 19 or Next.js) handle errors inside
PostCSS transforms to render a nice error overlay. This works well and
gives immediate feedback that something went wrong. However, even when
dependencies are registered before an error is thrown, these frameworks
_will not consider changes to these dependencies anymore_ when an error
occurs, as you can see in this Next.js example:
https://github.com/user-attachments/assets/985c9dd7-daf8-4628-b4ad-6543ef220954
To avoid conditions where errors are not recoverable, this PR makes it
so that these overlays will no longer show up in the app and only be
logged to the output console. This will need follow-up upstream work
before we can revisit this.
## Test plan
- Tested with the repro in #17295. The error can now be recovered from.
- Tested with a Next.js app where the issue in the screencast above is
now no longer happening.
- Added an integration test for errors in `@import`-ed files
- Added a unit test for the changed `@apply` behavior.
Closes#17508
This PR fixes another issue we found that caused dev builds with Next.js
and Turbopack to resolve the CSS file that was saved one revision before
the latest update.
When debugging this we noticed that the PostCSS entry is called twice
for every one update when changing the input CSS file directly. That was
caused by the input file itself being added as a _dependency_ so you
would first get the callback that a _dependency_ has updated (at which
point we look at the file system and figure out we need a full-rebuild
because the input.css file has changed) and then another callback for
when the _input file_ has updated. The problem with the second callback
was that the file-system was already scanned for updates and since this
includes the `mtimes` for the input file, we seemingly thought that the
input file did not change. However, the issue is that the first callback
actually came with an outdated PostCSS input AST...
We found that this problem arises when you register the input CSS as a
dependency of itself. This is not expected and we actually guard against
this in the PostCSS client. However, we found that the input `from`
argument is _a relative path when using Next.js with Turbopack_ so that
check was not working as expected.
## Test plan
Added the change to the repro from #17508 and it seems to work fine now.
https://github.com/user-attachments/assets/2acb0078-f961-4498-be1a-b1c72d5ceda1
Also added a unit test to ensure we document that the input file path
can be a relative path.
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR further improves the `color-mix(…)` polyfill to create a
reasonable fallback if dynamic values that can not statically be
resolved are used. This refers to either the use of `currentcolor` or
any variables that are not static theme variables.
Here are two examples that now generate a reasonable fallback instead of
not showing any color at all:
```css
.text-\\(--my-color\\)\\/\\(--my-opacity\\) {
color: var(--my-color);
}
@supports (color: color-mix(in lab, red, red)) {
.text-\\(--my-color\\)\\/\\(--my-opacity\\) {
color: color-mix(in oklab, var(--my-color) var(--my-opacity), transparent);
}
}
```
```css
.text-current\\/50 {
color: currentColor;
}
@supports (color: color-mix(in lab, red, red)) {
.text-current\\/50 {
color: color-mix(in oklab, currentColor 50%, transparent);
}
}
```
## Test plan
- Made sure the test diffs are looking reasonable
- Tested this on a production site with `<p className="text-shadow-lg/50
[--my-color:red] text-shadow-(color:--my-color)">shadow test</p>`
- Browsers that do not support `color-mix(…)` will properly show a red
shadow now albeit with 100% opacity: iOS 15.5 and Chrome 110
- Browsers that I have tested to make sure it still works there with
opacity: Firefox 127, Firefox 128, Latest Chrome, Safari, Firefox
- Browsers that do show a black shadow because of `var(…)var(…)` being
chained with no space by lightningcss: Chrome 111
This PR fixes an issue where if you use Next.js with `--turbopack` a
race condition happens because the `@tailwindcss/postcss` plugin is
called twice in rapid succession.
The first call sees an update and does a partial update with the new
classes. Next some internal `mtimes` are updated. The second call
therefore doesn't see any changes anymore because the `mtimes` are the
same, therefore it's serving its stale data.
Fixes: #17508
## Test plan
- Tested with the repro provided in #17508
- Added a new unit test that calls into the PostCSS plugin directly for
the same change from the same JavaScript run-loop.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This PR changes how polyfills for `@property` are inserted. The main
motivation is to remove the need to rely on the correct placement of
`@layer base;`—Something that's not really required right not in
Tailwind CSS v4 and we'd like to keep it this way.
The idea is that the polyfills are inserted for you automatically. To
ensure they always take precedence, we insert an empty `@layer
properties;` at the top of the CSS file so that later, when we emit all
`@property` rules and their fallback, we can use this new named layer to
ensure the rules have a higher order.
Unfortunately, just putting `@layer properties;` at the beginning of a
file would not work as `lightningcss` incorrectly hoists all content
into the first occurrence of a layer name meaning these rules might be
inserted _before_ eventual external imports:

To work around this, we have to insert that layer name after any
eventual remaining external `@imports` for now.
## Test plan
- Updated snapshot tests
- Deployed a new version of the website with the patch applied to ensure
it works across browsers:
https://tailwindcss-com-git-legacy-browsers-tailwindlabs.vercel.app/.
Tested on: Safari on iOS 15.5, Safari on iOS 16.0, Firefox 127, Firefox
128, Chrome 110, Chrome latest, Safari latest, Firefox latest
This PR fixes an issue where polyfills were injected at the top, but
they should be after `@import` and body-less `@layer` rules.
This is necessary in case you are using Google fonts like this for
example:
```css
@import url('https://fonts.google.com');
@import "tailwindcss";
```
While the `@import url(…);` sits above `@import "tailwindcss";` in the
final generated CSS we injected the polyfills at the very beginning.
This PR will inject the polyfills after the first AST Node that is not:
1. A comment
2. An external import — `@import url(…)`
3. A body-less layer — `@layer foo, bar, baz;`
The snapshots look a little confusing, but that's because Lightning CSS
is optimizing the output and moving things around a bit:
<img width="1482" alt="image"
src="https://github.com/user-attachments/assets/a0552c8b-93df-4e1d-ad90-8b8abf9492b1"
/>
[Lightning CSS
Playground](https://lightningcss.dev/playground/index.html#%7B%22minify%22%3Afalse%2C%22customMedia%22%3Atrue%2C%22cssModules%22%3Afalse%2C%22analyzeDependencies%22%3Afalse%2C%22targets%22%3A%7B%22chrome%22%3A6225920%7D%2C%22include%22%3A0%2C%22exclude%22%3A0%2C%22source%22%3A%22%40layer%20theme%2C%20base%2C%20components%2C%20utilities%3B%5Cn%5Cn%40supports%20(((-webkit-hyphens%3A%20none))%20and%20(not%20(margin-trim%3A%20inline)))%20or%20((-moz-orient%3A%20inline)%20and%20(not%20(color%3A%20rgb(from%20red%20r%20g%20b))))%20%7B%5Cn%20%20%40layer%20base%20%7B%5Cn%20%20%20%20*%2C%20%3Abefore%2C%20%3Aafter%2C%20%3A%3Abackdrop%20%7B%5Cn%20%20%20%20%20%20--tw-font-weight%3A%20initial%3B%5Cn%20%20%20%20%7D%5Cn%20%20%7D%5Cn%7D%5Cn%5Cn%40layer%20theme%20%7B%5Cn%20%20%3Aroot%2C%20%3Ahost%20%7B%5Cn%20%20%20%20--font-sans%3A%20ui-sans-serif%2C%20system-ui%2C%20sans-serif%2C%20%5C%22Apple%20Color%20Emoji%5C%22%2C%20%5C%22Segoe%20UI%20Emoji%5C%22%2C%20%5C%22Segoe%20UI%20Symbol%5C%22%2C%20%5C%22Noto%20Color%20Emoji%5C%22%3B%5Cn%20%20%20%20--font-mono%3A%20ui-monospace%2C%20SFMono-Regular%2C%20Menlo%2C%20Monaco%2C%20Consolas%2C%20%5C%22Liberation%20Mono%5C%22%2C%20%5C%22Courier%20New%5C%22%2C%20monospace%3B%5Cn%20%20%20%5Cn%20%20%7D%5Cn%7D%5Cn%5Cn%40layer%20base%20%7B%5Cn%20%20*%2C%20%3Aafter%2C%20%3Abefore%2C%20%3A%3Abackdrop%20%7B%5Cn%20%20%20%20box-sizing%3A%20border-box%3B%5Cn%20%20%20%20border%3A%200%20solid%3B%5Cn%20%20%20%20margin%3A%200%3B%5Cn%20%20%20%20padding%3A%200%3B%5Cn%20%20%7D%5Cn%7D%5Cn%5Cn%40layer%20utilities%20%7B%5Cn%20%20.text-2xl%20%7B%5Cn%20%20%20%20font-size%3A%20var(--text-2xl)%3B%5Cn%20%20%20%20line-height%3A%20var(--tw-leading%2C%20var(--text-2xl--line-height))%3B%5Cn%20%20%7D%5Cn%7D%5Cn%5Cn%40property%20--tw-font-weight%20%7B%5Cn%20%20syntax%3A%20%5C%22*%5C%22%3B%5Cn%20%20inherits%3A%20false%5Cn%7D%22%2C%22visitorEnabled%22%3Afalse%2C%22visitor%22%3A%22%7B%5Cn%20%20Color(color)%20%7B%5Cn%20%20%20%20if%20(color.type%20%3D%3D%3D%20'rgb')%20%7B%5Cn%20%20%20%20%20%20color.g%20%3D%200%3B%5Cn%20%20%20%20%20%20return%20color%3B%5Cn%20%20%20%20%7D%5Cn%20%20%7D%5Cn%7D%22%2C%22unusedSymbols%22%3A%5B%5D%2C%22version%22%3A%22local%22%7D)
Fixes: #17494
This PR improves the compatibility with Tailwind CSS v4 with unsupported
browsers with the goal to greatly improve compatibility with Safari 15.
To make this work, this PR makes the following changes to all code
- Change `oklab(…)` default theme values to use a percentage in the
first place (so instead of `--color-red-500: oklch(0.637 0.237 25.331);`
we now define it as `--color-red-500: oklch(63.7% 0.237 25.331);` since
this syntax has much broader support on Safari).
- Polyfill `@property` with a `@supports` query targeting older versions
of Safari and Firefox *
- Create fallbacks for the `color-mix(…)` function that use _inlined
color values from your theme_ so that they can be computed a compile
time by `lightningcss`. These fallbacks will convert to srgb to increase
compatibility.
- Create fallbacks for the _relative color_ feature used in the new
shadow utilities and using `color-mix(…)` in case _relative color_ is
applied on `currentcolor` (due to limited browser support)
- Create fallbacks for gradient interpolation methods (e.g. to support
`bg-linear-to-r/oklab`)
- Polyfill `@media` queries range syntax.
## A simplified example
Given this example CSS input:
```css
@import 'tailwindcss';
@source inline('from-cyan-500/50 bg-linear-45');
```
Here's the updated output CSS including the newly added polyfills and
updated `oklab` values:
```css
.bg-linear-45 {
--tw-gradient-position: 45deg;
background-image: linear-gradient(var(--tw-gradient-stops));
}
@supports (background-image: linear-gradient(in lab, red, red)) {
.bg-linear-45 {
--tw-gradient-position: 45deg in oklab;
}
}
.from-cyan-500\\/50 {
--tw-gradient-from: oklab(71.5% -.11682 -.08247 / .5);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
@supports (color: color-mix(in lab, red, red)) {
.from-cyan-500\\/50 {
--tw-gradient-from: color-mix(in oklab, var(--color-cyan-500) 50%, transparent);
}
}
:root, :host {
--color-cyan-500: oklch(71.5% .143 215.221);
}
@supports (((-webkit-hyphens: none)) and (not (margin-trim: 1lh))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
@layer base {
*, :before, :after, ::backdrop {
--tw-gradient-position: initial;
--tw-gradient-from: #0000;
--tw-gradient-via: #0000;
--tw-gradient-to: #0000;
--tw-gradient-stops: initial;
--tw-gradient-via-stops: initial;
--tw-gradient-from-position: 0%;
--tw-gradient-via-position: 50%;
--tw-gradient-to-position: 100%;
}
}
}
@property --tw-gradient-position {
syntax: "*";
inherits: false
}
@property --tw-gradient-from {
syntax: "<color>";
inherits: false;
initial-value: #0000;
}
@property --tw-gradient-via {
syntax: "<color>";
inherits: false;
initial-value: #0000;
}
@property --tw-gradient-to {
syntax: "<color>";
inherits: false;
initial-value: #0000;
}
@property --tw-gradient-stops {
syntax: "*";
inherits: false
}
@property --tw-gradient-via-stops {
syntax: "*";
inherits: false
}
@property --tw-gradient-from-position {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0%;
}
@property --tw-gradient-via-position {
syntax: "<length-percentage>";
inherits: false;
initial-value: 50%;
}
@property --tw-gradient-to-position {
syntax: "<length-percentage>";
inherits: false;
initial-value: 100%;
}
```
## \* A note on `@property` polyfills and CSS modules
On Next.js, CSS module files are required to be _pure_, meaning that all
selectors must either be scoped to a class or an ID. Fortunatnyl for us,
this does not apply to `@property` rules which we've been using before
to initialize CSS variables.
However, since we're now bringing back the `@property` polyfills, that
would cause unexpected rules to be exported from the CSS file as this:
```css
@reference "tailwindcss";
.skew {
@apply skew-7;
}
```
Would turn to the following file:
```css
.skew {
/* … */
}
@supports (/*…*/) {
@layer base {
*, :before, :after, ::backdrop {
--tw-gradient-position: initial;
}
}
}
@property /* … */
```
Notice that this adds a `*` selector which is not considered pure.
Unfortunately there is no way for us to silence this warning or work
around it, as the dependency causing this errors
([`postcss-modules-local-by-default`](https://github.com/css-modules/postcss-modules-local-by-default))
is bundled into Next.js. To work around crashes, these polyfills will
not apply to CSS modules processed by the PostCSS extension for now.
## Testing on tailwindcss.com
To see the changes in effect, take a look at this screencast that
compares tailwindcss.com on iOS 15.5 with a version that has the patches
of this PR applied:
https://github.com/user-attachments/assets/1279d6f5-3c63-4f30-839c-198a789f4292
## Test plan
- Tested on tailwindcss.com via a preview build:
https://tailwindcss-com-git-legacy-browsers-tailwindlabs.vercel.app/
- Updated tests
- Ensure we also test on Chrome 111, Safari 16.4, Firefox 128 to
make sure we have no regressions. Also tested on Safari 16.4, 15.5, 18.0
This PR is a follow-up PR for:
https://github.com/tailwindlabs/tailwindcss/pull/17433
In the other PR we allow scanning CSS files for extracting usages of CSS
variables. This is important for `.module.css` files that reference
these variables but aren't in the same big AST of the main CSS file.
This PR also makes sure to watch for changes in those registered CSS
files and re-extract the variables when they change.
This PR took a bit longer than expected because I was trying to make
sure that writing to `./dist/out.css` works without infinite-looping
(e.g.: we had issues with this in Tailwind CSS v3 with webpack).
But I couldn't reproduce the issue at all. I did had some code that
tried to detect if the CSS file contained license headers and skip in
(because then it's very likely an output CSS file) but even without it
the tests were fine.
I setup integration tests with `@tailwindcss/cli` itself, and with tools
that use webpack. Added a test for Next.js, and a dedicated webpack test
as well.
Even without tests, locally, I couldn't reproduce an infinite loop due
to changes in an output CSS file...
Eventually dropped the code that tries to detect output CSS files.
One thing to keep in mind is that if you change any of your "main" CSS
files, then we will trigger a full rebuild anyway, so this change is
only required for unrelated CSS files (like CSS module files) that use
CSS variables.
## Test plan
1. Added integration tests for the CLI and Next.js
2. Added new dedicated test for webpack
This PR reworks a unit test that created a file in the project root and
then proceeded by scanning everything in the git root for candidates.
The issue specifically is that with the `.debug/` folder, our project
root can grow quite a bit which makes this test slower the more you work
on other tests...
To fix this we now simply create a tmp folder with only that one test
file. 🚀
This PR adds a new source detection feature: `@source not "…"`. It can
be used to exclude files specifically from your source configuration
without having to think about creating a rule that matches all but the
requested file:
```css
@import "tailwindcss";
@source not "../src/my-tailwind-js-plugin.js";
```
While working on this feature, we noticed that there are multiple places
with different heuristics we used to scan the file system. These are:
- Auto source detection (so the default configuration or an `@source
"./my-dir"`)
- Custom sources ( e.g. `@source "./**/*.bin"` — these contain file
extensions)
- The code to detect updates on the file system
Because of the different heuristics, we were able to construct failing
cases (e.g. when you create a new file into `my-dir` that would be
thrown out by auto-source detection, it'd would actually be scanned). We
were also leaving a lot of performance on the table as the file system
is traversed multiple times for certain problems.
To resolve these issues, we're now unifying all of these systems into
one `ignore` crate walker setup. We also implemented features like
auto-source-detection and the `not` flag as additional _gitignore_ rules
only, avoid the need for a lot of custom code needed to make decisions.
High level, this is what happens after the now:
- We collect all non-negative `@source` rules into a list of _roots_
(that is the source directory for this rule) and optional _globs_ (that
is the actual rules for files in this file). For custom sources (i.e
with a custom `glob`), we add an allowlist rule to the gitignore setup,
so that we can be sure these files are always included.
- For every negative `@source` rule, we create respective ignore rules.
- Furthermore we have a custom filter that ensures files are only read
if they have been changed since the last time they were read.
So, consider the following setup:
```css
/* packages/web/src/index.css */
@import "tailwindcss";
@source "../../lib/ui/**/*.bin";
@source not "../../lib/ui/expensive.bin";
```
This creates a git ignore file that (simplified) looks like this:
```gitignore
# Auto-source rules
*.{exe,node,bin,…}
*.{css,scss,sass,…}
{node_modules,git}/
# Custom sources can overwrite auto-source rules
!lib/ui/**/*.bin
# Negative rules
lib/ui/expensive.bin
```
We then use this information _on top of your existing `.gitignore`
setup_ to resolve files (i.e so if your `.gitignore` contains rules e.g.
`dist/` this line is going to be added _before_ any of the rules lined
out in the example above. This allows negative rules to allow-list your
`.gitignore` rules.
To implement this, we're rely on the `ignore` crate but we had to make
various changes, very specific, to it so we decided to fork the crate.
All changes are prefixed with a `// CHANGED:` block but here are the
most-important ones:
- We added a way to add custom ignore rules that _extend_ (rather than
overwrite) your existing `.gitignore` rules
- We updated the order in which files are resolved and made it so that
more-specific files can allow-list more generic ignore rules.
- We resolved various issues related to adding more than one base path
to the traversal and ensured it works consistent for Linux, macOS, and
Windows.
## Behavioral changes
1. Any custom glob defined via `@source` now wins over your `.gitignore`
file and the auto-content rules.
- Resolves#16920
3. The `node_modules` and `.git` folders as well as the `.gitignore`
file are now ignored by default (but can be overridden by an explicit
`@source` rule).
- Resolves#17318
- Resolves#15882
4. Source paths into ignored-by-default folders (like `node_modules`)
now also win over your `.gitignore` configuration and auto-content
rules.
- Resolves#16669
5. Introduced `@source not "…"` to negate any previous rules.
- Resolves#17058
6. Negative `content` rules in your legacy JavaScript configuration
(e.g. `content: ['!./src']`) now work with v4.
- Resolves#15943
7. The order of `@source` definitions matter now, because you can
technically include or negate previous rules. This is similar to your
`.gitingore` file.
9. Rebuilds in watch mode now take the `@source` configuration into
account
- Resolves#15684
## Combining with other features
Note that the `not` flag is also already compatible with [`@source
inline(…)`](https://github.com/tailwindlabs/tailwindcss/pull/17147)
added in an earlier commit:
```css
@import "tailwindcss";
@source not inline("container");
```
## Test plan
- We added a bunch of oxide unit tests to ensure that the right files
are scanned
- We updated the existing integration tests with new `@source not "…"`
specific examples and updated the existing tests to match the subtle
behavior changes
- We also added a new special tag `[ci-all]` that, when added to the
description of a PR, causes the PR to run unit and integration tests on
all operating systems.
[ci-all]
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Prepare the 4.0.16 release.
~~Also added a commit to mark the `--value('…')` and `--modifier('…')`
with literals strings as an experimental feature (aka not shipped in
this PR). But we can revert that commit if we still want to ship it in
4.0.16 instead of 4.1.~~
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
<!--
👋 Hey, thanks for your interest in contributing to Tailwind!
**Please ask first before starting work on any significant new
features.**
It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create an issue to first
discuss any significant new features. This includes things like adding
new utilities, creating new at-rules, or adding new component examples
to the documentation.
https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md
-->
Closes#17194.
This reverts commit d6d913ec39e2a4cc0a70e9d21c484c6ed95d40ae.
The initial fix does breaks older versions of Chrome (where text won't
render with a color for the placeholder at all anymore) and the usage of
the _relative colors_ features also means it'll require a much newer
version of Safari/Firefox/Chrome to work correctly. The implementation
was also wrong as it always set alpha to the specific percent instead of
applying it additively (note that this can be fixed with `calc(alpha *
opacity)` though).
Instead we decided to fix this by adding a `@supports` query to
Preflight that only targets browsers that aren't affected by the crash.
We currently use the following workaround:
```css
/*
Set the default placeholder color to a semi-transparent version of the current text color in browsers that do not
crash when using `color-mix(…)` with `currentColor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194)
*/
@supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or
(contain-intrinsic-size: 1px) /* Safari 17+ */ {
::placeholder {
color: color-mix(in oklab, currentColor 50%, transparent);
}
}
```
## Test plan
When testing the `color-mix(currentColor)` vs `oklab(from currentColor
…)` we created the following support matrix. I'm extending it with _our
fix_ which is the fix ended up using:
| Browser | Version | `color-mix(… currentColor …)` | `oklab(from
currentColor …)` | `@supports { color-mix(…) }` |
| ------- | ------- | ----------------------------- |
---------------------------- | ------- |
| Chrome | 111 | ❌ | ❌ | ❌ |
| Chrome | 116 | ✅ | ❌ | ✅ |
| Chrome | 131+ | ✅ | ✅ | ✅ |
| Safari | 16.4 | 💥 | ❌ | ❌ |
| Safari | 16.6+ | ✅ | ❌ | ❌ |
| Safari | 18+ | ✅ | ✅ | ✅ |
| Firefox | 128 | ✅ | ❌ | ✅ |
| Firefox | 133 | ✅ | ✅ | ✅ |
Note that on Safari 16, this change makes it so that the browser does
not crash yet it still won't work either. That's because now the browser
will fall back to the default placeholder color instead. We used the
following play to test the fix: https://play.tailwindcss.com/RF1RYbZLKY
Closes#16945
This PR changes the `--theme(…)` function now return CSS `var(…)`
definitions unless used in places where `var(…)` is not valid CSS (e.g.
in `@media (width >= theme(--breakpoint-md))`):
```css
/* input */
@theme {
--color-red: red;
}
.red {
color: --theme(--color-red);
}
/* output */
:root, :host {
--color-red: red;
}
.red {
color: var(--color-red);
}
```
Furthermore, this adds an `--theme(… inline)` option to the `--theme(…)`
function to force the resolution to be inline, e.g.:
```css
/* input */
@theme {
--color-red: red;
}
.red {
color: --theme(--color-red inline);
}
/* output */
.red {
color: red;
}
```
This PR also changes preflight and the default theme to use this new
`--theme(…)` function to ensure variables are prefixed correctly.
## Test plan
- Added unit tests and a test that pulls in the whole preflight under a
prefix theme.
It was added in #2729 to override line heights set on the body by
modern-normalize. However, it appears that modern-normalize never
included any line-height definitions—only a font-family rule was
present.
Ref:
https://github.com/sindresorhus/modern-normalize/blob/v1.1.0/modern-normalize.css
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Closes#17194
This PR works around a crash when rendering opacity on `currentColor`
(as used by the placeholder styles in preflight) on Safari 16.4 and
Safari 16.5. Unfortunately it seems that the [`color-mix(…)` function is
not compatible with `currentColor` for these versions of
Safari](https://stackoverflow.com/questions/76436497/the-color-mix-property-involving-currentcolor-causes-safari-to-crash).
We tried a few different ways to work around this without success:
- Using an `@supports` media query to target these Safari versions and
overwriting the placeholder still makes these browsers crash.
- Changing the way we apply opacity to `currentColor` in core doesn't
seem to work for non-placeholder values:
https://github.com/tailwindlabs/tailwindcss/issues/17194#issuecomment-2728949181
However, a wrong opacity is still better than a complete browser crash.
The work-around of using the `oklab(…)` function does seem to work for
`::placeholder` styles in preflight though according to our testing so
this PR applies this change to preflight.
## Test plan
- See https://play.tailwindcss.com/WSsSTLHu8h?file=css
- Tested on Chrome/Safari 16.4/Safari 18.3/Firefox
<img width="564" alt="Screenshot 2025-03-17 at 11 32 47"
src="https://github.com/user-attachments/assets/cfd0db71-f39a-4bc0-bade-cea70afe50ae"
/>
Fixes#16636
This PR enables URL rebasing for PostCSS. Furthermore it fixes an issue
where transitive imports rebased against the importer CSS file instead
of the input CSS file. While fixing this we noticed that this is also
broken in Vite right now and that our integration test swallowed that
when testing because it did not import any Tailwind CSS code and thus
was not considered a Tailwind file.
## Test plan
- Added regression integration tests
- Also validated it against the repro of
https://github.com/tailwindlabs/tailwindcss/issues/16962:
<img width="1149" alt="Screenshot 2025-03-05 at 16 41 01"
src="https://github.com/user-attachments/assets/85396659-d3d0-48c0-b1c7-6125ff8e73ac"
/>
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Fixes#16725
When using `@reference "tailwindcss";` inside a separate CSS root (e.g.
Svelte `<style>` components, CSS modules, etc.), we have no guarantee
that the CSS variables will be defined in the main stylesheet (or if
there even is one). To work around potential issues with this we decided
in #16676 that we would emit all used CSS variables from the `@theme`
inside the `@reference` block.
However, this is not only a bit surprising but also unexpected in CSS
modules and Next.js that **requires CSS module files to only create
scope-able declarations**. To fix this issue, we decided to not emit CSS
variables but instead ensure all `var(…)` calls we create for theme
values in reference mode will simply have their fallback value added.
This ensures styles work as-expected even if the root Tailwind file does
not pick up the variable as being used or _if you don't add a root at
all_. Furthermore we do not duplicate any variable declarations across
your stylesheets and you still have the ability to change variables at
runtime.
## Test plan
- Updated snapshots everywhere (see diff)
- New Next.js CSS modules integration test
<!--
👋 Hey, thanks for your interest in contributing to Tailwind!
**Please ask first before starting work on any significant new
features.**
It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create an issue to first
discuss any significant new features. This includes things like adding
new utilities, creating new at-rules, or adding new component examples
to the documentation.
https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md
-->
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR re-enables the changes necessary to remove unused theme
variables and keyframes form your CSS.
This change was initially landed as #16211 and then later reverted in
#16403 because we found some unexpected interactions with using `@apply`
and CSS variables in multi-root setups like CSS modules or Vue inline
`<style>` blocks that were no longer seeing their required variables
defined.
This issue is fixed by now ensuring that theme variables that are
defined within an `@reference "…"` boundary will still be emitted in the
generated CSS when used (as this would otherwise not generate a valid
stylesheet).
So given the following input CSS:
```css
@reference "tailwindcss";
.text-red {
@apply text-red-500;
}
```
We will now compile this to:
```css
@layer theme {
:root, :host {
--text-red-500: oklch(0.637 0.237 25.331);
}
}
.text-red {
color: var(--text-red-500);
}
```
This PR also improves the initial implementation to not mark theme
variables as used if they are only used to define other theme variables.
For example:
```css
@theme {
--font-sans:
ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
--font-mono:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
--default-font-family: var(--font-sans);
--default-mono-font-family: var(--font-mono);
}
.default-font-family {
font-family: var(--default-font-family);
}
```
This would be reduced to the following now as `--font-mono` is only used
to define another variable and never used outside the theme block:
```css
:root, :host {
--font-sans:
ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
--default-font-family: var(--font-sans);
}
.default-font-family {
font-family: var(--default-font-family);
}
```
## Test plan
- See updated unit and integration tests
- Validated it works end-to-end by using a SvelteKit example
This PR fixes an issue where installing a specific version of
`@tailwindcss/postcss` and `tailwindcss` could still result in a version
mismatch. This is because we were relying on `^4.0.6` for example
instead of `4.0.6`.
This PR now pins all these versions to prevent this:
```
❯ pnpm why tailwindcss
devDependencies:
@tailwindcss/postcss 4.0.5
├─┬ @tailwindcss/node 4.0.6
│ └── tailwindcss 4.0.6
└── tailwindcss 4.0.5
```
This reverts #16211
We found some unexpected interactions with using `@apply` and CSS
variables in multi-root setups like CSS modules or Vue inline `<style>`
blocks that were broken due to that change. We plan to re-enable this
soon and include a proper fix for those scenarios.
## Test plan
- Updated snapshots
- Tested using the CLI in a new project:
<img width="1523" alt="Screenshot 2025-02-10 at 13 08 42"
src="https://github.com/user-attachments/assets/defe0858-adb3-4d61-9d2c-87166558fd68"
/>
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
<!--
👋 Hey, thanks for your interest in contributing to Tailwind!
**Please ask first before starting work on any significant new
features.**
It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create an issue to first
discuss any significant new features. This includes things like adding
new utilities, creating new at-rules, or adding new component examples
to the documentation.
https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md
-->
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>