Custom Giscus Theme to Match Your Site
How to create custom CSS themes for Giscus so comments blend seamlessly with your site palette instead of looking like a GitHub widget dropped in.
Problem Statement
In a previous post, I covered how to keep Giscus in sync with the site’s dark mode toggle. That solved the switching problem, but the comments section still looked… off.
Even after switching between light and dark_dimmed, Giscus shipped its own GitHub-flavored palette. The blues, grays, and button styles clashed with the warm tones of this blog. The widget felt like an embedded GitHub page rather than a natural part of the site.
Solutions
The core idea is simple: replace the built-in Giscus themes with custom CSS files hosted on your own domain, then point the widget at those files by requesting Giscus accepts a full URL in the data-theme attribute. If you hand it a URL ending in .css, it loads that stylesheet inside the iframe.
Implementation
Disclaimer
- The snippets below focus on the theming path only.
Step 1: Extract the Default Giscus Variables
Giscus has a set of CSS custom properties based on theme. The easiest starting point is to grab the default themes directly from Giscus and use them as your base:
Save them locally and start modifying.
The properties fall into a few groups:
- Syntax highlighting —
--color-prettylights-syntax-* - Buttons —
--color-btn-* - Canvas and foreground —
--color-canvas-*,--color-fg-* - Borders —
--color-border-* - Accent —
--color-accent-*
Step 2: Map Your Site Palette onto the Variables
We will replaced the Giscus defaults with values that match our site:
/* public/giscus/light.css (excerpt) */main { --color-fg-default: #261813; --color-fg-muted: #7d6a61; --color-canvas-default: #fffaf4; --color-canvas-inset: #f5e7df; --color-border-default: #e9d7ce; --color-accent-fg: #ce6141; --color-accent-emphasis: #ce6141; --color-btn-text: #261813; --color-btn-bg: #f5e7df; --color-btn-border: #e9d7ce; --color-btn-primary-bg: #ce6141; --color-btn-primary-border: #ce6141; /* ... rest of the variables */}The dark variant follows the same structure:
/* public/giscus/dark.css (excerpt) */main { --color-fg-default: #f5e9df; --color-fg-muted: #a89082; --color-canvas-default: #14100e; --color-canvas-inset: #2b1f1b; --color-border-default: #4a352f; --color-accent-fg: #f0906d; --color-accent-emphasis: #df7b5c; --color-btn-text: #f5e9df; --color-btn-bg: #2b1f1b; --color-btn-border: #4a352f; --color-btn-primary-bg: #df7b5c; --color-btn-primary-border: #df7b5c; /* ... rest of the variables */}Step 3: Host the Files and Point Giscus at Them
Place both files in your public directory (e.g. public/giscus/light.css and public/giscus/dark.css). In Astro, everything under public/ is served at the site root, so they can be access public at /giscus/light.css and /giscus/dark.css.
When initializing the Giscus script, pass the full URL instead of a theme name:
const mode = document.documentElement.classList.contains('dark') ? 'dark' : 'light';script.setAttribute('data-theme', `${location.origin}/giscus/${mode}.css`);When switching themes at runtime, we will do the same:
function currentGiscusTheme() { const mode = document.documentElement.classList.contains('dark') ? 'dark' : 'light'; return `${location.origin}/giscus/${mode}.css`;}Thanks to this, Giscus will fetch the CSS from your domain and applies it inside the iframe.
Step 4: Handle CORS
Since the Giscus iframe on giscus.app fetches CSS from your domain, you need to allow cross-origin requests. Without the right headers, the iframe silently fails to load your stylesheet.
Development Environment (Netlify)
I added a header rule in netlify.toml:
[[headers]] for = "/giscus/*" [headers.values] Access-Control-Allow-Origin = "*" Cache-Control = "public, max-age=31536000, immutable"Local Environment (Astro + Vite)
Locally, Astro’s dev server also needs to cooperate, if we dont do this, the custom css for Giscus will only work on Development Environment. In order to configure this run in local server, we need to do two things in astro.config.mjs:
First, allowlist the Giscus origin so Astro’s security middleware does not block the request:
security: { allowedDomains: [{ hostname: 'giscus.app', protocol: 'https' }]},Second, add a small Vite plugin to set the CORS header on the dev server:
vite: { plugins: [ { name: 'giscus-cors', configureServer(server) { server.middlewares.use((req, res, next) => { if (req.url?.startsWith('/giscus/')) { res.setHeader('Access-Control-Allow-Origin', '*'); } next(); }); } } ]}Conclusion
After following all mentioned steps, the Giscus comments section now have the same look and feel with the site. The key insight is that we need to make Giscus accepts a full stylesheet URL in data-theme.
You've completed Styling Giscus for Your Site. Back to series overview