How to Create Custom CSS Themes and Styling in Docusaurus: Complete Guide
You can customize Docusaurus styling by declaring global CSS files in the customCss option of @docusaurus/preset-classic for site-wide theming, or using .module.css files alongside React components for scoped, collision-free styles.
Docusaurus provides a flexible architecture for visual customization through the Infima CSS framework without requiring you to eject core theme components. By leveraging the customCss configuration option in the classic preset, you can inject global styles that override default design tokens while maintaining upgrade compatibility. This article examines the source implementation in facebook/docusaurus to demonstrate how to create custom CSS themes and styling in Docusaurus using both global stylesheets and CSS Modules.
Adding Global CSS with the customCss Option
The classic theme exposes a customCss configuration property that resolves file paths relative to your site directory and injects them into the final stylesheet bundle. According to the implementation in packages/docusaurus-theme-classic/src/index.ts (lines 43-80), the preset automatically handles path resolution and bundling.
Configuring the Classic Preset
Create a global stylesheet—conventionally at src/css/custom.css—then reference it in your docusaurus.config.js or docusaurus.config.ts file:
// docusaurus.config.js
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
theme: {
customCss: ['./src/css/custom.css'],
},
},
],
],
};
The customCss property is defined in the TypeScript declarations at packages/docusaurus-theme-classic/src/theme-classic.d.ts (lines 27-33) and validated by the Joi schema in packages/docusaurus-theme-classic/src/options.ts (lines 485-500). The option accepts either a single string or an array of strings representing relative file paths.
Overriding Infima CSS Variables
Infima, the underlying CSS framework, exposes CSS custom properties that control colors, typography, and spacing. Override these in your global CSS file to theme the entire site:
/* src/css/custom.css */
:root {
--ifm-color-primary: #ff6600;
--ifm-color-primary-dark: #e65c00;
--ifm-font-family-base: 'Open Sans', sans-serif;
--ifm-link-color: #0066ff;
}
.navbar__brand {
font-weight: bold;
}
Because Docusaurus injects your custom CSS after the default Infima stylesheet, your rules naturally take precedence without requiring !important overrides.
Loading Multiple Stylesheets
The customCss option accepts an array, allowing you to organize styles into separate concerns such as variables, layouts, and component overrides:
customCss: [
'./src/css/variables.css',
'./src/css/layout.css',
],
As implemented in packages/docusaurus-theme-classic/src/index.ts (lines 71-79), Docusaurus resolves each path using path.resolve(context.siteDir, p) before pushing the modules into the Webpack/Rspack stylesheet pipeline.
Using CSS Modules for Component-Scoped Styles
For styles that should apply only to specific theme components or custom React components, create CSS Module files with the .module.css extension alongside your component files:
// src/theme/NavbarItem/CustomItem/index.tsx
import React from 'react';
import styles from './styles.module.css';
export default function CustomItem() {
return <div className={styles.box}>Hello, styled world!</div>;
}
/* src/theme/NavbarItem/CustomItem/styles.module.css */
.box {
padding: 0.5rem 1rem;
background: var(--ifm-color-primary);
color: white;
border-radius: 4px;
}
Docusaurus uses the standard Webpack/Rspack CSS Module loader to process these files. The loader automatically generates unique class names and exposes them via the imported styles object, preventing naming collisions across your codebase. No additional configuration is required to enable this behavior.
Implementation Details from the Source Code
The theme entry point at packages/docusaurus-theme-classic/src/index.ts orchestrates how custom CSS enters the build pipeline:
- Path Resolution: Each string in
customCssis resolved relative to the site directory using Node'spath.resolve - Validation: The Joi schema in
packages/docusaurus-theme-classic/src/options.tsensures paths are valid strings or arrays of strings - Injection: Resolved modules are added to the bundler's entry points, ensuring your styles are included in the production build and hot-reloaded during development
The official create-docusaurus template demonstrates this configuration in packages/create-docusaurus/templates/classic-typescript/docusaurus.config.ts, providing a ready-to-use example of customCss integration.
Summary
- Global CSS: Declare paths in the
customCssoption of@docusaurus/preset-classicto inject site-wide styles that override Infima variables - CSS Modules: Use
.module.cssfiles for scoped, collision-free styling of specific components without affecting the global namespace - Multiple Files: Pass an array to
customCssto split your styles into logical, maintainable files - Variable Theming: Override
--ifm-*CSS variables in your global stylesheet to retheme the entire design system consistently - Source Authority: The classic theme resolves and validates these options in
packages/docusaurus-theme-classic/src/index.tsandoptions.ts
Frequently Asked Questions
Can I use multiple CSS files for different theming concerns?
Yes. The customCss option accepts an array of file paths, allowing you to separate variables, layouts, and component-specific overrides into distinct files. Docusaurus resolves each path with path.resolve(context.siteDir, p) as shown in packages/docusaurus-theme-classic/src/index.ts (lines 71-79) and bundles them in the order specified.
Why should I use CSS Modules instead of global CSS?
CSS Modules generate unique class names scoped to the importing component, preventing style leakage and naming conflicts across your site. Use them when building custom theme components or swizzled components where styles should remain isolated. Global CSS is better suited for overriding Infima variables or applying site-wide branding that affects multiple pages.
Where does Docusaurus validate the customCss configuration?
The classic theme validates the customCss option using a Joi schema defined in packages/docusaurus-theme-classic/src/options.ts (lines 485-500). TypeScript definitions in packages/docusaurus-theme-classic/src/theme-classic.d.ts (lines 27-33) provide compile-time type safety, ensuring your configuration contains valid file path strings before the build process begins.
How do I ensure my custom styles override the default theme?
Docusaurus injects your customCss files after the default Infima stylesheet in the bundle pipeline defined in packages/docusaurus-theme-classic/src/index.ts. Because of the cascading nature of CSS, your rules take precedence when selectors have equal specificity. For Infima variables, redefining them in :root automatically propagates changes throughout the component tree without needing to target specific selectors.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →