Free Astro tutorials for complete beginners.

Beginner's Guide to Astro

How to Add Dark Mode to an Astro Site

Make your Astro site follow the phone or computer's dark setting with a few lines of CSS. Then add a button that remembers the choice, with no flash on load.

Tested on Astro 7.4

Cover image for How to Add Dark Mode to an Astro Site
In this guide
  1. Step 1: Put your colors in one place
  2. Step 2: Load the CSS in your layout
  3. Step 3: Add a button (optional)
  4. Step 4: Stop the flash on page load
  5. Step 5: Build and try it
  6. Make it better
  7. Where to go next
  8. Frequently asked questions

Dark mode in Astro takes two parts. Part one is pure CSS. Your site turns dark when the visitor’s phone or computer is set to dark. Part two is optional. It adds a button that lets people pick, and it remembers the pick. A tiny script in the head stops the page flashing white.

Step 1: Put your colors in one place

Make a CSS file at src/styles/global.css. Give each color a name. Then use the names, not the colors, for the page:

:root {
  --bg: #ffffff;
  --text: #1a1a1a;
  --link: #5b21b6;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bg: #121212;
    --text: #eeeeee;
    --link: #c4b5fd;
    color-scheme: dark;
  }
}

:root[data-theme='dark'] {
  --bg: #121212;
  --text: #eeeeee;
  --link: #c4b5fd;
  color-scheme: dark;
}

body {
  background: var(--bg);
  color: var(--text);
}

a {
  color: var(--link);
}

Here’s how to read it:

  • The first block is your light colors. They are the normal ones.
  • The @media block says: if the device is set to dark, use the dark colors. The :not([data-theme='light']) part means “unless the visitor picked light.” You’ll use that in step 3.
  • The last block says: if the visitor picked dark, use dark. This works even when the device is set to light.
  • color-scheme tells the browser to make scroll bars and form boxes match.

Yes, the dark colors are written twice. That’s on purpose. One copy follows the device. The other follows the button.

Step 2: Load the CSS in your layout

Open your layout, like src/layouts/Layout.astro. Import the CSS file at the top:

---
import '../styles/global.css';
---

Run npm run dev. Now switch your computer or phone to dark mode. The site turns dark too. If that’s all you want, you can stop here. No JavaScript needed.

Step 3: Add a button (optional)

Some people want to choose. Create src/components/ThemeToggle.astro:

<button id="theme-toggle" type="button">Light or dark?</button>

<script>
  const button = document.getElementById('theme-toggle');

  button.addEventListener('click', () => {
    const root = document.documentElement;
    const deviceIsDark = matchMedia('(prefers-color-scheme: dark)').matches;
    const isDark = root.dataset.theme === 'dark' || (!root.dataset.theme && deviceIsDark);
    const next = isDark ? 'light' : 'dark';
    root.dataset.theme = next;
    localStorage.setItem('theme', next);
  });
</script>

When someone clicks, the script works out if the page is dark right now. Then it flips it. It writes data-theme="light" or data-theme="dark" on the <html> tag. Your CSS from step 1 reacts to that. It also saves the choice in localStorage. That’s a small storage box in the visitor’s own browser.

Step 4: Stop the flash on page load

The button saves the choice. But the next page has to read it back. If it reads it too late, the page shows light for a split second, then jumps to dark. That’s the flash.

The fix is a tiny script at the top of the page, inside <head>. Here’s the full layout with everything in place:

---
import '../styles/global.css';
import ThemeToggle from '../components/ThemeToggle.astro';
---

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>My Site</title>
    <script is:inline>
      const saved = localStorage.getItem('theme');
      if (saved) document.documentElement.dataset.theme = saved;
    </script>
  </head>
  <body>
    <ThemeToggle />
    <slot />
  </body>
</html>

The is:inline part matters. Normally Astro bundles your scripts and loads them later. is:inline tells Astro to leave this one right where it is. So it runs before the browser draws anything. The right colors are there from the very first moment.

If nothing was saved, the script does nothing. Then the CSS just follows the device.

Step 5: Build and try it

Run npm run build, then npm run preview, and open the page. Click the button. The colors flip. Reload the page. Your choice stays, with no white flash.

We ran this on a fresh Astro 7.4 project. The build passed. In the built page, the small script came first in the head, before any styles. Then we tested it in a browser on a computer set to dark. With nothing saved, the page was dark. One click made it light and saved “light”. After a reload it was still light, even though the computer was set to dark.

Building with Claude? Paste this guide into Claude Code and ask it to do it for you.

Make it better

  • Put the button in your menu. Drop <ThemeToggle /> into your header component instead of the body.
  • Add more colors. Borders, cards and code boxes can get their own names, like --border. Add each one to all three blocks.
  • Check your contrast. Light gray text on a dark gray page can be hard to read. Keep text bright enough.

Where to go next

Frequently asked questions

Do I need JavaScript for dark mode?

No. CSS alone can follow the device setting. You only need a little JavaScript if you want a button that lets people pick for themselves.

Why does my page flash white before turning dark?

The saved choice was read too late. Put the small script inside the head, with is:inline, so it runs before the page is drawn.

Where is the choice saved?

In localStorage, a small storage box in each visitor's browser. Nothing is sent to you, and each person's choice stays on their own device.

Stuck on a step?

Check the common errors guide, look up a word in the glossary, or ask the friendly people in the official Astro and EmDash Discords. How to ask so people can help.

Keep reading

  1. How to Register a Domain Name and Connect It to Your Astro Site

    Buy a domain for about $11 a year, then point it at Cloudflare or Netlify. We did this for astronursery.com, and these are the exact steps.

    Tutorials, 8 minute read, November 8, 2026

  2. How to Add Google Analytics to an Astro Site (and a Simpler Option)

    Paste Google's tag into your Astro layout with one small change, is:inline, and it works on every page. Or use Cloudflare Web Analytics, which needs no cookie banner.

    Tutorials, 6 minute read, November 8, 2026

  3. How to Add a Contact Form to an Astro Site (No Server Needed)

    A static site can't receive form submissions on its own. A free form service can. Here's a working contact form in ten lines, with spam protection built in.

    Tutorials, 7 minute read, November 8, 2026

Try "blog", "deploy" or "EmDash".