ui: Dark mode not working because of `@layer base`, demo repo provided

Hi and congrats for making these beautiful components!

I notice that dark mode isn’t working on a fresh install:

  • ran npx create-next-app@latest, didn’t choose to use the app directory
  • ran npx shadcn-ui init
  • ran npm i next-themes
  • added <ThemeProvider attribute="class"> in _app.tsx

The result: we notice that the dark class is present (thanks to prefers-color-scheme: dark), but it’s the variables from :root that are applied, not the ones from .dark:

image

Demo repository: https://github.com/Zwyx/shadcn-ui-dark-mode-demo deployed at https://zwyx.github.io/shadcn-ui-dark-mode-demo/

Notes:

  • Removing @layer base from globals.css fixes this.
  • This issue is very similar to https://github.com/shadcn/ui/issues/278 although I’m not using the app directory, and I’m not able to fix it…

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 6
  • Comments: 38 (12 by maintainers)

Commits related to this issue

Most upvoted comments

Had the same issue, fixed it with :root[class~="dark"] instead of .dark in style.css

style.css for reference: https://github.com/langfuse/langfuse-docs/blob/e9ba748e0e53e5d9179ffc0040839108d1930621/style.css#L39

I had the same problem until I found out I had to add themeColor to my root layout metadata, following the template repo here. No other special changes to tailwind.config.js nor globals.css.

@Zwyx I notice you’re not using the app dir in your demo repo, but since themeColor gets converted into <meta name="theme-color" /> tags (according to Nextjs docs), maybe you could try copy-pasting the appropriate tags into your project and see if dark mode works afterwards?

Adding this in app/layout.tsx worked for me

import { Metadata } from 'next';

export const metadata: Metadata = {
  themeColor: [
    { media: '(prefers-color-scheme: light)', color: 'white' },
    { media: '(prefers-color-scheme: dark)', color: 'black' },
  ],
};

Hi Nico and thanks for your message. Good find! That indeed fixed it. I simply added the following in _documents.tsx:

	<Head>
		<meta
			name="theme-color"
			media="(prefers-color-scheme: light)"
			content="white"
		/>
		<meta
			name="theme-color"
			media="(prefers-color-scheme: dark)"
			content="black"
		/>
	</Head>

I don’t see the relation between @layer base and theme-color though. It could be interesting to dig, but I’m not keen to dive in a deep rabbit hole.

I also noticed something else: this problem was happening in Chrome, but not in Firefox.

I guess with this third workaround, we could close the issue if you’d like, Shadcn?

I was also trying to enable dark theme with shadcn-ui yesterday and ended up on this thread after searching for a fix. Here are some observations and possible solutions:

TL;DR:

  • Add safelist: ['dark'] to your tailwind.config.ts if you don’t mind changing such file and you want an officially supported fix; OR
  • Replace the .dark selector in your globals.css file file with :root[class~="dark”] (thanks @marcklingen).

Explanation

  • This page states that Tailwind scans all files matched by your content glob patterns (e.g. ["**/*.{ts,tsx}”] will scan for all files ending with .ts or .tsx relative to your project root).
  • Their documentation suggests that libraries should write styles without using Tailwind’s @layer feature, in order to ensure that “Tailwind always includes those styles in your CSS”. Additionally,

The way Tailwind scans your source code for classes is intentionally very simple — we don’t actually parse or execute any of your code in the language it’s written in, we just use regular expressions to extract every string that could possibly be a class name.

Therefore, it seems that the main cause for the dark theme not being applied is that Tailwind has not found any project files containing a dark class call, thus removing it from the final bundle.

Workarounds and Fixes

Using Storybook and Next.js, the following fixes have worked for me:

NOTE: Depending on your setup, Tailwind may cache styles even after removed, so I recommend to restart your server after applying/removing any of the following fixes/hacks.

  • Defining the .dark class outside of the @layer in your globals.css file will always include dark theme styles in the final bundle, but it might break styling since the original code written by @shadcn could rely on the order of @layers, affecting the priority of class resolution.

  • Add any object containing a string with dark in a file scanned by Tailwind. Not recommended, since this is a hack and it’s hard to locate and debug:

    const triggerTailwind = ['dark'];

  • The above may be the reason why many different solutions in this thread work:

  • Adding the global css file ./src/app/globals.css to your content property in tailwind.config.ts works as Tailwind will probably scan it and find the .dark class, however Tailwind also explicitly recommends against adding .css files to your content folder:

    It’s also important that you don’t scan any CSS files — configure Tailwind to scan your templates where your class names are being used, never the CSS file that Tailwind is generating.

  • I really liked @marcklingen’s solution: by replacing .dark with :root[class~="dark"] in the globals.css file, it seems that Tailwind identifies the "dark" token as a string, or maybe it has a special rule for such selectors inside @layers. This will continue to work unless Tailwind changes its scanning system in a way that affects the special :root[class~="dark"] selector.

  • If you don’t mind changing your tailwind.config.ts file, these strategies are also valid:

    • Adding { raw: '<div class=“dark”>’, extension: 'html' } as Raw Content inside your content property works, but it is hacky and there is a better way below:

    • Recommended: add the safelist property and provide the dark class to it: safelist: ["dark"].

@shadcn Thank you for creating such an awesome collection of components!

Had the same issue, fixed it with :root[class~="dark"] instead of .dark in style.css

style.css for reference: https://github.com/langfuse/langfuse-docs/blob/e9ba748e0e53e5d9179ffc0040839108d1930621/style.css#L39

Thank you, this worked for me as well. I just set the styles up with both targets, class and root:

  .dark,
  :root[class~="dark"] {
    ...
  }

Excellent. It makes much more sense now. I didn’t realise Tailwind uses @layer. Thanks a lot Rafael!

I think we now have everything to take a decision on this issue:

TL;DR:

  • reason the problem is not experienced by everyone:

    • it only happens when dark: isn’t used anywhere, which makes Tailwind dropping it from the CSS.
  • fix: EITHER

    • replace .dark by :root[class~="dark"] in globals.css – there is an old PR for that, I’d be happy to update if it’s the solution that is chosen. OR
    • add safelist: ['dark'] to tailwind.config.ts, which sounds a bit easier.

@shadcn, which one of these two solutions would you like to see implemented?

Thanks again heaps to @rafaelcalpena for his massive help!

@ndinata worked for me too!

@shadcn We should either add this to the docs or somehow fix it!

@hgaron-blockbar thanks for trying to fix this! I confirm that changing content to ["**/*.{ts,tsx}"], or include "./styles/**/*.css" in it, solves the issue. Good find!

Looks like it is. I still have to use the root selector, e.g.:

.dark,
:root[class~='dark'] {
  ...
}

@Zwyx I haven’t yet gotten to the root of the issue, but it does seem to be some sort of config issue. Updating this as the content array restores the styles as expected:

content: ["**/*.{ts,tsx}"]

You can also update your original content array to include the styles folder, which also works:

content: [
		"./pages/**/*.{ts,tsx}",
		"./pages/*.{ts,tsx}",
		"./components/**/*.{ts,tsx}",
		"./styles/**/*.css",
	],

But that is not recommended per the tailwind docs. 🤔

Seems in this repro it only works when that css file is included.

@AchrafGarai you won’t be able to allow your users to set the them they want; dark theme will only be applied if it’s their OS’s preference. Which isn’t a problem, as long as it’s what you want.

Had the same issue, fixed it with :root[class~="dark"] instead of .dark in style.css

style.css for reference: https://github.com/langfuse/langfuse-docs/blob/e9ba748e0e53e5d9179ffc0040839108d1930621/style.css#L39

@shadcn this works well for us

For me, on a Vite/React app, the problem was forgetting to import the global.css file into my App.tsx.

Had the same issue, fixed it with :root[class~="dark"] instead of .dark in style.css

style.css for reference: https://github.com/langfuse/langfuse-docs/blob/e9ba748e0e53e5d9179ffc0040839108d1930621/style.css#L39

Thank you! Worked just fine. I was searching for a solution for hours.

Hi Cibi, thanks for your contribution. ~However it looks like I won’t be able to help on this issue anymore: I can’t reproduce it. It’s probably since a Chrome update.~

My bad. I still have the issue. It requires a full restart of the dev server to see it again, so I missed it before. Glad I double checked.

Discussion continued at https://github.com/shadcn-ui/ui/pull/938

Got the same problem, following the previous suggestions and replacing the content in the the tailwind config to the following content to ["**/*.{ts,tsx}"] solves the issue.

Update your globals.css

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 47.4% 11.2%;

    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;

    --popover: 0 0% 100%;
    --popover-foreground: 222.2 47.4% 11.2%;

    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;

    --card: 0 0% 100%;
    --card-foreground: 222.2 47.4% 11.2%;

    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;

    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;

    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;

    --destructive: 0 100% 50%;
    --destructive-foreground: 210 40% 98%;

    --ring: 215 20.2% 65.1%;

    --radius: 0.5rem;
  }

  .dark {
    --background: 224 71% 4%;
    --foreground: 213 31% 91%;

    --muted: 223 47% 11%;
    --muted-foreground: 215.4 16.3% 56.9%;

    --accent: 216 34% 17%;
    --accent-foreground: 210 40% 98%;

    --popover: 224 71% 4%;
    --popover-foreground: 215 20.2% 65.1%;

    --border: 216 34% 17%;
    --input: 216 34% 17%;

    --card: 224 71% 4%;
    --card-foreground: 213 31% 91%;

    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 1.2%;

    --secondary: 222.2 47.4% 11.2%;
    --secondary-foreground: 210 40% 98%;

    --destructive: 0 63% 31%;
    --destructive-foreground: 210 40% 98%;

    --ring: 216 34% 17%;

    --radius: 0.5rem;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
    font-feature-settings: "rlig" 1, "calt" 1;
  }
}

@layer utilities {
  .step {
    counter-increment: step;
  }

  .step:before {
    @apply absolute w-8 h-8 bg-muted rounded-full font-medium text-center text-base inline-flex items-center justify-center -indent-px;
    @apply ml-[-41px];
    content: counter(step);
  }
}

@media (max-width: 640px) {
  .container {
    @apply px-4;
  }
}