Skip to main content
2025-03-20

Dark Mode and Theming

as-folio ships with a polished dark mode implementation that matches al-folio’s visual identity. The toggle is in the navbar (☀/☽ icon). Preference is persisted to localStorage and restored before the page renders to avoid any flash of unstyled content.

How it works

1. FOUC prevention

An inline <script> in <head> — before any stylesheet — reads localStorage and sets data-theme="dark" on <html> before the browser paints:

// Runs synchronously before first paint
const theme = localStorage.getItem('theme') ?? 'light';
document.documentElement.setAttribute('data-theme', theme);

Because this script is is:inline in Astro, it is never deferred or bundled — it runs at the earliest possible moment.

2. CSS custom properties

All colours live in src/styles/_colors.css as CSS custom properties, mirroring al-folio’s variable names exactly:

:root {
  --global-bg-color: #ffffff;
  --global-text-color: #000000;
  --global-theme-color: #b509ac;
  /* ... */
}

[data-theme='dark'] {
  --global-bg-color: #1c1c1c;
  --global-text-color: #ffffff;
  --global-theme-color: #b509ac; /* same accent in dark */
  /* ... */
}

Every component references these variables rather than hard-coded colours, so toggling data-theme instantly re-paints the whole page without a single JavaScript re-render.

3. The toggle component

ThemeToggle.astro writes to localStorage and updates data-theme:

<script>
  const toggle = document.getElementById('theme-toggle');
  toggle?.addEventListener('click', () => {
    const current = document.documentElement.getAttribute('data-theme');
    const next = current === 'dark' ? 'light' : 'dark';
    document.documentElement.setAttribute('data-theme', next);
    localStorage.setItem('theme', next);
  });
</script>

4. Giscus comment theme sync

When comments are enabled, Giscus.astro observes data-theme changes with a MutationObserver and posts a message to the Giscus <iframe> to switch its own theme:

new MutationObserver(() => {
  const theme = document.documentElement.getAttribute('data-theme');
  giscusFrame.contentWindow.postMessage(
    { giscus: { setConfig: { theme: theme === 'dark' ? 'dark' : 'light' } } },
    'https://giscus.app',
  );
}).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });

Customising the accent colour

The theme colour is set in src/config/site.ts under theme.color. To change the accent, update light and dark (and optionally hoverLight and hoverDark) — each takes any CSS colour, or 'auto' for the built-in default:

theme: {
  color: {
    light: '#0070f3', /* Vercel blue */
    dark: '#68c0d9',
    hoverLight: 'auto',
    hoverDark: 'auto',
  },
},

That’s all — every badge, link highlight, tag pill, and interactive element picks it up automatically.