Skip to content

Conversation

nshafer
Copy link
Contributor

@nshafer nshafer commented Aug 20, 2025

Originally there was a problem where dark: tailwind variants would only work if the user selected the "system" theme and their browser prefers-color-scheme: dark was set. The PR #6334 attempted to fix it, so that manually selecting the "dark" theme would then enable dark: variants, but this then broke dark: variants so they wouldn't apply if the user selected "system" and had prefers-color-scheme: dark, since that PR enables dark: variants only in the case of [data-theme="dark"] matching. This PR attempts to rectify this situation so that the dark: variant works correctly in both cases.

It does this by setting the desired system/light/dark selection in a data-phx-theme attribute as "system-light", "system-dark", "light" or "dark", then instructing tailwind to use that data element to enable dark: variants with a wildcard match:

@custom-variant dark (&:where([data-phx-theme*=dark], [data-phx-theme*=dark] *));

This works properly if the system changes the preferred color scheme, such as to be light during the day, dark at night, as a lot of OSes support as an option these days.

Additionally, as a side effect, this makes it much easier to customize which theme is being used for the light and dark modes, and enables the user to use built-in DaisyUI themes, or more easily customize their own, by configuring two constants in the "root.html.heex". I included a couple small comments to point out how to do this.

And finally, I fixed the secondary color of the elixir dark custom theme to be a gray instead of the same color as the primary. Take that or leave it if you want.

FAQ:

  • Can't we just tell tailwind to enable dark: variants if prefers-color-scheme: dark or the data-theme="dark" element is on the doc root? No, there's no way with CSS that I know of to do an OR between a @media query and a normal class match.
/* DOESN'T WORK: this is invalid CSS */
.dark\:bg-blue-200 {
    @media (prefers-color-scheme: dark) or &:where([data-theme=dark], [data-theme=dark] *) {
        background-color: var(--color-blue-200);
    }
}

But either way this would break dark: variants again, by applying them to a system that prefers dark, but selected the "light" override. Same as:

/* Valid CSS, but then `dark:` variants apply even if the "light" theme is selected, so back to the original bug. */
.dark\:bg-blue-200 {
    @media (prefers-color-scheme: dark) {
        background-color: var(--color-blue-200);
    }
    &:where([data-theme=dark], [data-theme=dark] *) {
        background-color: var(--color-blue-200);
    }
}
  • Do we need this complexity? Yes, if we want a theme selector that supports system choice, or overriding that system choice, I see no other option after playing with this a lot. If it's just system choice with no toggle widget, the default tailwind dark: variant support works fine. If we removed the system choice, and just defaulted to light, with an override to dark (or queried system preference and changed the default with JS), then it would also be much simpler code. Supporting system or an override, and wanting dark: variants to work with both requires this complexity as far as I can figure out.

  • Is the part where the user can configure their preferred light and dark themes necessary to fix the bug? No, not strictly. That part could be removed if desired, but it added only a tiny bit of code and made it less complex for devs, IMO. And who doesn't like being able to play with themes? With the extra consts it allows devs to remove the custom themes completely, simplifying their "app.css" and use the built-in DaisyUI themes, and also connects the dots between the DaisyUI docs and the custom theme selector in Phoenix projects, imo. Either way, some kind of data attribute needs to be on the <html> element to fix the bug.

  • Why change the custom theme names? I did this to distinguish them from the built-in DaisyUI "dark" and "light" themes; to be more explicit and minimize confusion. Fixing the secondary color was just a pet-peeve I had. I would also like to swap the order, the "phoenix-light" theme should be first, then dark, imo. =P

  • Does this work without JS Well, no, not as I've given in this PR. If we want a theme toggle, which requires JS, then JS needs to set up the data attributes, so that the custom dark: variant we configure in "app.css" will work. If the user desires to support no-JS browsers, then they can do one of the following:

    1. Remove the theme toggle, all the code from "root.html.heex" and the @custom-variant dark from "app.css" so that Tailwind/DaisyUI goes back to just selecting the theme based on the @media (prefers-color-scheme: dark) {} media query.
    2. Configure the following @custom-variant and accept the extra bloat in their output "app.css":
@custom-variant dark {
  @media (prefers-color-scheme: dark) {
    &:not(:where([data-phx-theme*=light], [data-phx-theme*=light] *)) {
      @slot
    }
  }
  &:where([data-phx-theme*=dark], [data-phx-theme*=dark] *) {
    @slot
  }
}

That will result in every dark: variant in their source files output both of those cases, which can be a lot of extra css! Since the default phoenix projects assume JS, I did not include this in the PR, but leave it here for 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.

1 participant