Part 2 of 2 · Styling Giscus for Your Site

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.

Published on

2 min read

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.

Share

About the Author

Duong Le
Software Engineer

Duong Le is a software engineer with over six years of experience building and maintaining web applications, from early-stage products to production systems.

His background includes a master's degree in Computer Science and published research at two academic conferences (CITA 2023, IMCOM 2019).