Last active
March 3, 2025 08:47
-
-
Save ryuheechul/049723ce665a4ff83022b88851aed8e7 to your computer and use it in GitHub Desktop.
Revisions
-
ryuheechul revised this gist
Mar 3, 2025 . 1 changed file with 6 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,10 @@ ## Meta During a development of one of my web project, I discovered the gap between DaisyUI and tailwindcss on how each handles the system's theme (via [`prefers-color-scheme: dark`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)) and manual theme override (via something like `[data-theme]` in case to give users to override against the system theme). Although the proposed the solution from me (you can see by continouly reading this gist) is not very complex, this can be confusing for people who have no similar experience on handing these. Hence sharing it as gist. ## The Problem [DaisyUI (v5)](https://daisyui.com/) and [tailwindcss (v4)](https://tailwindcss.com/) both work (automatically) well with `prefers-color-scheme: dark` (aka following OS theme). And DaisyUI allow you to override the theme with `[data-theme]`, which tailwindcss does not work with it unless you manually override the config as describe as https://tailwindcss.com/docs/dark-mode#using-a-data-attribute. @@ -11,7 +15,7 @@ And DaisyUI allow you to override the theme with `[data-theme]`, which tailwindc However after the manual configuration as above, this now does not rely on `prefers-color-scheme: dark` at all unlike the case with DaisyUI. In addition, their suggestion regarding how to deal with it with javascript, https://tailwindcss.com/docs/dark-mode#with-system-theme-support doesn't give you the full picture (well it's just a simple example, so fair enough) and it misses handling the case of having to react on system's theme change when it's supposed to be "auto" (e.g. when no "[data-theme]" set). ## Solution 1 -
ryuheechul created this gist
Mar 3, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,125 @@ ## The Problem [DaisyUI (v5)](https://daisyui.com/) and [tailwindcss (v4)](https://tailwindcss.com/) both work well with [`prefers-color-scheme: dark`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) (aka following OS theme). And DaisyUI allow you to override the theme with `[data-theme]`, which tailwindcss does not work with it unless you manually override the config as describe as https://tailwindcss.com/docs/dark-mode#using-a-data-attribute. ```css /* as tailwindcss suggested it from https://tailwindcss.com/docs/dark-mode#using-a-data-attribute */ @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); ``` However after the manual configuration as above, this now does not rely on `prefers-color-scheme: dark` at all unlike the case with DaisyUI. In addition, their suggestion regarding how to deal with it with javascript, https://tailwindcss.com/docs/dark-mode#with-system-theme-support doesn't give you the full picture (well it's just a simple example, so fair enough and) that doesn't gracefully work (e.g. it doesn't react to theme change, when it's supposed to be "auto" - e.g. when no "[data-theme]" set - and system theme changed). ## Solution 1 This is a working solution as we manually observe media query change event and act accordingly to it. ```css /* as tailwindcss suggested it from https://tailwindcss.com/docs/dark-mode#using-a-data-attribute */ @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); ``` ```js document.addEventListener( 'theme-should-change', // using a custom event to manipulate `dataset.theme`, you might handle this differently than using an event like me (e) => { const theme = e.detail; // let's assume that it will be one of ['auto', 'dark', 'light'] // we interpret the auto to manually set 'dark' and 'light' so tailwindcss understands it via our manual css config above if (theme === 'auto') { if (window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.dataset.theme = 'dark'; } else { document.documentElement.dataset.theme = 'light'; } } else { document.documentElement.dataset.theme = theme; } }, true, ); // this is a very specific way (e.g. get localstorage saved by AlpineJS's $persist plugin) to get what the theme user selected should be - you can just ignore this if it doesn't make sense to you function extract$persist(name) { // add '_x_' prefix for alpinejs convention // use `JSON.parse` to unqoute quoted string value return JSON.parse(localStorage.getItem(`_x_${name}`)); } window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => { const newColorScheme = event.matches ? 'dark' : 'light'; const remeberedTheme = extract$persist('globalTheme'); if (remeberedTheme === 'auto' && newColorScheme === 'dark') { const event = new CustomEvent('theme-should-change', { detail: newColorScheme }); document.dispatchEvent(event); } }); // again, you can ignore this block too to focus on the main topic of this gist { // early call to prevent visual theme flickers as AlpineJS would react to this too late to cause a visual flicker const remeberedTheme = extract$persist('globalTheme'); if (['auto', 'dark', 'light'].includes(remeberedTheme)) { const event = new CustomEvent('theme-should-change', { detail: remeberedTheme }); document.dispatchEvent(event); } } ``` ## Solution 2 This solution should be considered as better than the first one above and There is less javascript code and it just works as the same way as DaisyUI would without too many manipulating logics. ```diff css - @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); + @custom-variant dark { + /* priotize [data-theme=dark] */ + &:where([data-theme=dark], [data-theme=dark] *) { + @slot; + } + /* fallback to the default behavior as following `prefers-color-scheme: dark` only when there is no `data-theme` is set already */ + @media (prefers-color-scheme: dark) { + &:not([data-theme], [data-theme] *) { + @slot; + } + } + } ``` ```diff js document.addEventListener( 'theme-should-change', // using a custom event to manipulate `dataset.theme`, you might handle this differently than using an event like me (e) => { const theme = e.detail; // let's assume that it will be one of ['auto', 'dark', 'light'] - // we interpret the auto to manually set 'dark' and 'light' so tailwindcss understands it via our manual css config above if (theme === 'auto') { - if (window.matchMedia('(prefers-color-scheme: dark)').matches) { - document.documentElement.dataset.theme = 'dark'; - } else { - document.documentElement.dataset.theme = 'light'; - } + // now simply delete and let `prefers-color-scheme` selector to do the work with the absence of `[data-theme]` + delete document.documentElement.dataset.theme; } else { document.documentElement.dataset.theme = theme; } }, true, ); // ... - window.matchMedia('(prefers-color-scheme: dark)').addEvenmediatListener('change', (event) => { - const newColorScheme = event.matches ? 'dark' : 'light'; - const remeberedTheme = extract$persist('globalTheme'); - if (remeberedTheme === 'auto' && newColorScheme === 'dark') { - const event = new CustomEvent('theme-should-change', { detail: newColorScheme }); - document.dispatchEvent(event); - } - }); // ... ```