Skip to content

Improve compatibility with Safari 15 #17435

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

Merged
merged 28 commits into from
Apr 1, 2025
Merged

Improve compatibility with Safari 15 #17435

merged 28 commits into from
Apr 1, 2025

Conversation

philipp-spiess
Copy link
Member

@philipp-spiess philipp-spiess commented Mar 28, 2025

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:

@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:

.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:

@reference "tailwindcss";

.skew {
  @apply skew-7;
}

Would turn to the following file:

.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) 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:

Screen.Recording.2025-03-28.at.17.52.25.mov

Open questions

  • Should we make the @property polyfill not require an @layer base and instead place the generated CSS on the top of the file?
  • Add fallbacks for newly introduced usages of the relative color syntax.
  • Decide if we're ok with how we actually implement the @media range query workaround

Test plan

@philipp-spiess philipp-spiess force-pushed the fix/safari-15 branch 3 times, most recently from cb15f4e to 521c5ed Compare March 31, 2025 12:01
@philipp-spiess philipp-spiess marked this pull request as ready for review March 31, 2025 14:18
@philipp-spiess philipp-spiess requested a review from a team as a code owner March 31, 2025 14:18
Copy link
Member

@RobinMalfait RobinMalfait left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't functionally tested yet, but wanted to leave these notes already.


// Work around an issue where the media query range syntax transpilation
// generates code that is invalid with `@media` queries level 3.
out = out.replaceAll('@media not (', '@media not all and (')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅

Comment on lines +4932 to +4934
.text-red-500\\/\\(--my-half\\) {
color: #fb2c3680;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sad that this means that you can't change the --my-half value at runtime, but if it's not supported what can you do haha.

@@ -697,7 +708,7 @@ export async function compileAst(
}

if (!utilitiesNode) {
compiled ??= optimizeAst(ast, designSystem)
compiled ??= optimizeAst(ast, designSystem, opts.polyfills)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should store the polyfills on the designSystem itself 🤔

@philipp-spiess philipp-spiess merged commit 156afc6 into main Apr 1, 2025
7 checks passed
@philipp-spiess philipp-spiess deleted the fix/safari-15 branch April 1, 2025 11:33
philipp-spiess added a commit that referenced this pull request Apr 1, 2025
@silvenon
Copy link

silvenon commented Apr 6, 2025

Sorry for the late comment, but it is my understanding that with this PR merged the general Safari support for basic Tailwind v4 goes from 16.4 to 16.2 (color-mix is supported down to 16.2). Is that true?

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

Successfully merging this pull request may close these issues.

4 participants