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

Add support for inline option when defining @theme values #14095

Merged
merged 5 commits into from
Aug 2, 2024

Conversation

adamwathan
Copy link
Member

@adamwathan adamwathan commented Jul 31, 2024

This PR adds support for a new inline option when defining a @theme block that tells Tailwind to use raw theme values for utilities instead of referencing the corresponding generated CSS variable.

/* Input */
@theme inline {
  --color-red-500: #ef4444;
  /* ... */
}


/* Example output */
:root {
  --color-red-500: #ef4444;
}

.text-red-500 {
  color: #ef4444;
}

This can be composed with the existing reference option in case you want to define a @theme block as both reference (so the variables aren't generated) and inline:

/* Input */
@theme inline reference {
  --color-red-500: #ef4444;
  /* ... */
}


/* Example output */
.text-red-500 {
  color: #ef4444;
}

Since you can have multiple @theme blocks, you can even define some values normally and some as inline based on how you're using them. For example you might want to use inline for defining literal tokens like --color-red-500, but include the variable for tokens that you want to be able to theme like --color-primary:

/* Input */
@theme inline {
  --color-red-500: #ef4444;
  /* ... */
}

@theme {
  --color-primary: var(--color-red-500);
}


/* Example output */
:root {
  --color-red-500: #ef4444;
  --color-primary: var(--color-red-500);
}

.text-red-500 {
  color: #ef4444;
}

.text-primary {
  color: var(--color-primary, var(--color-red-500));
}

Breaking changes

Prior to this PR, you could @import a stylesheet that contained @theme blocks as reference by adding the reference keyword to your import:

@import "./my-theme.css" reference;

Now that reference isn't the only possible option when declaring your @theme, this syntax has changed to a new theme(…) function that accepts reference and inline as potential space-separated values:

@import "./my-theme.css";
@import "./my-theme.css" theme(reference);
@import "./my-theme.css" theme(inline);
@import "./my-theme.css" theme(reference inline);

If you are using the @import … reference option with an earlier alpha release, you'll need to update your code to @import … theme(reference) once this PR lands in a release.

Motivation

This PR is designed to solve an issue pointed out in #14091.

Prior to this PR, generated utilities would always reference variables directly, with the raw value as a fallback:

/* Input */
@theme {
  --color-red-500: #ef4444;
  /* ... */
}


/* Example output */
:root {
  --color-red-500: #ef4444;
}

.text-red-500 {
  color: var(--color-red-500, #ef4444);
}

But this can create issues with variables resolving to an unexpected value when a theme value is referencing another variable defined on :root.

For example, say you have a CSS file like this:

:root, .light {
  --text-fg: #000; 
}

.dark {
  --text-fg: #fff;
}

@theme {
  --color-fg: var(--text-fg);
}

Without @theme inline, we'd generate this output if you used the text-fg utility:

:root, .light {
  --text-fg: #000; 
}

.dark {
  --text-fg: #fff;
}

:root {
  --color-fg: var(--text-fg);
}

.text-fg {
  color: var(--color-fg, var(--text-fg));
}

Now if you wrote this HTML, you're probably expecting your text to be the dark mode color:

<div class="dark">
  <h1 class="text-fg">Hello world</h1>
</div>

But you'd actually get the light mode color because of this rule:

:root {
  --color-fg: var(--text-fg);
}

.text-fg {
  color: var(--color-fg, var(--text-fg));
}

The browser will try to resolve the --color-fg variable, which is defined on :root. When it tries to resolve the value, it uses the value of var(--text-fg) as it would resolve at :root, not what it would resolve to based on the element that has the text-fg class.

So var(--color-fg) resolves to #000 because var(--text-fg) resolved to #000 at the point in the tree where the browser resolved the value of var(--color-fg).

By using @theme inline, the .text-fg class looks like this:

.text-fg {
  color: var(--text-fg);
}

With this definition, the browser doesn't try to resolve --color-fg at all and instead resolves --text-fg directly which correctly resolves to #fff as expected.

packages/tailwindcss/src/index.ts Outdated Show resolved Hide resolved
packages/tailwindcss/src/index.ts Outdated Show resolved Hide resolved
packages/tailwindcss/src/index.ts Outdated Show resolved Hide resolved
packages/tailwindcss/src/index.ts Outdated Show resolved Hide resolved
Destructuring works, but it's creating an unnecessary array. We are
currently interested in whether the `inline` and/or `reference` values
exist in the `themeOptions` list.

We already know that the very first item is going to be `@theme`, so we
can skip over that by providing the `fromIndex` when using
`.includes(value, fromIndex)`
Comment on lines +36 to +50
function parseThemeOptions(selector: string) {
let isReference = false
let isInline = false

for (let option of segment(selector.slice(6) /* '@theme'.length */, ' ')) {
if (option === 'reference') {
isReference = true
} else if (option === 'inline') {
isInline = true
}
}

return { isReference, isInline }
}

Copy link
Member Author

@adamwathan adamwathan Aug 1, 2024

Choose a reason for hiding this comment

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

@RobinMalfait Decided to extract this function because the work was being done twice, and themeOptions didn't feel like a good name for an array that contained:

['@theme', 'reference', 'inline']

Refactored to a for loop to avoid the extra loop that calling includes twice would incur, but admittedly sad it's more code.

Copy link
Member

Choose a reason for hiding this comment

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

Nice, makes sense.

One potential thing we can do is if both options are true early return because there is no need to keep looping if everything is found already. But since @theme is so specific to Tailwind, there is no need for that optimization (yet).

@adamwathan adamwathan merged commit e000caa into next Aug 2, 2024
2 checks passed
@adamwathan adamwathan deleted the feat/theme-inline-option branch August 2, 2024 13:37
@marklawlor
Copy link

It is possible to apply @theme inline reference to the default theme or is only available for theme overrides?

The use case is NativeWind which is a JS implementation of CSS for React Native. While it can handle the CSS variables used in the theme, it would be more performant to inline them.

The workaround currently is to either use postcss/lightningcss to inline the variables or override every theme variable with @theme inline reference

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