$npx -y skills add rstackjs/agent-skills --skill rspress-custom-themeCustomize Rspress themes using CSS variables, Layout slots, component wrapping, or component ejection. Use when a user wants to change the look and feel of an Rspress site, override theme components, add custom navigation/sidebar/footer content, inject global providers, or modify
| 1 | # Rspress Custom Theme |
| 2 | |
| 3 | Guide for customizing Rspress (v2) themes. Rspress offers four levels of customization, from lightest to heaviest. Always prefer the lightest approach that meets the requirement — lighter approaches are more maintainable and survive Rspress upgrades. |
| 4 | |
| 5 | ## Workflow |
| 6 | |
| 7 | 1. **Understand the user's goal** — what do they want to change? (colors, layout, inject content, replace a component entirely?) |
| 8 | 2. **Pick the right level** using the decision flow below |
| 9 | 3. **Set up `theme/index.tsx`** if needed (Levels 1A, 3, 4 all need it) |
| 10 | 4. **Implement** following the patterns in this skill and reference files |
| 11 | 5. **Verify** the user's Rspress version is v2 (imports use `@rspress/core/*` not `rspress/*`) |
| 12 | |
| 13 | ## Decision Flow |
| 14 | |
| 15 | | User wants to... | Level | Approach | |
| 16 | | ---------------------------------------------------------------- | ----- | --------------------------- | |
| 17 | | Change brand colors, fonts, spacing, shadows | 1 | CSS variables | |
| 18 | | Adjust a specific component's style (borders, padding, etc.) | 2 | BEM class overrides | |
| 19 | | Add content around existing components (banners, footers, logos) | 3 | Layout slots (wrap) | |
| 20 | | Override MDX rendering (custom `<h1>`, `<code>`, etc.) | 3 | `components` slot | |
| 21 | | Wrap the app in a provider (state, analytics, auth) | 4 | Eject `Root` | |
| 22 | | Replace built-in icons (logo, GitHub, search, etc.) | — | Icon re-export | |
| 23 | | Completely replace a built-in component | 4 | Eject that component | |
| 24 | | Add a global floating component (back-to-top, chat widget) | — | `globalUIComponents` config | |
| 25 | | Control page layout structure (hide sidebar, blank page) | — | Frontmatter `pageType` | |
| 26 | |
| 27 | --- |
| 28 | |
| 29 | ## theme/index.tsx — The Entry Point |
| 30 | |
| 31 | Levels 1A, 3, and 4 all require a `theme/index.tsx` file in the project root (sibling to `docs/`). This is the single entry point for all theme customizations: |
| 32 | |
| 33 | ```text |
| 34 | project/ |
| 35 | ├── docs/ |
| 36 | ├── theme/ |
| 37 | │ ├── index.tsx # Theme entry — re-exports + overrides |
| 38 | │ ├── index.css # CSS variable / BEM overrides (optional) |
| 39 | │ └── components/ # Ejected components (Level 4) |
| 40 | └── rspress.config.ts |
| 41 | ``` |
| 42 | |
| 43 | Minimal setup: |
| 44 | |
| 45 | ```tsx |
| 46 | // theme/index.tsx |
| 47 | import './index.css'; // optional |
| 48 | export * from '@rspress/core/theme-original'; |
| 49 | ``` |
| 50 | |
| 51 | **Critical import rule**: Inside `theme/` files, always import from `@rspress/core/theme-original`. The path `@rspress/core/theme` resolves to your own `theme/index.tsx`, which causes circular imports. (In `docs/` MDX files, `@rspress/core/theme` is fine — it correctly points to your custom theme.) |
| 52 | |
| 53 | --- |
| 54 | |
| 55 | ## Level 1: CSS Variables |
| 56 | |
| 57 | Override CSS custom properties for brand colors, backgrounds, text, code blocks, and more. |
| 58 | |
| 59 | **Option A** — `theme/index.css` (use when you also have component overrides in `theme/index.tsx`): |
| 60 | |
| 61 | ```css |
| 62 | /* theme/index.css */ |
| 63 | :root { |
| 64 | --rp-c-brand: #7c3aed; |
| 65 | --rp-c-brand-light: #8b5cf6; |
| 66 | --rp-c-brand-dark: #6d28d9; |
| 67 | } |
| 68 | .dark { |
| 69 | --rp-c-brand: #a78bfa; |
| 70 | } |
| 71 | ``` |
| 72 | |
| 73 | **Option B** — `globalStyles` (use when you only need CSS changes, no component overrides): |
| 74 | |
| 75 | ```ts |
| 76 | // rspress.config.ts |
| 77 | export default defineConfig({ |
| 78 | globalStyles: path.join(__dirname, 'styles/custom.css'), |
| 79 | }); |
| 80 | ``` |
| 81 | |
| 82 | > **Full variable list**: Read `references/css-variables.md` for all available CSS variables with light/dark defaults. |
| 83 | |
| 84 | --- |
| 85 | |
| 86 | ## Level 2: BEM Class Overrides |
| 87 | |
| 88 | All built-in components follow BEM naming: `.rp-[component]__[element]--[modifier]`. |
| 89 | |
| 90 | Common targets: `.rp-nav`, `.rp-link`, `.rp-tabs`, `.rp-codeblock`, `.rp-codeblock__title`, `.rp-nav-menu__item--active`. |
| 91 | |
| 92 | Use these in your CSS file for targeted style changes when CSS variables aren't granular enough. |
| 93 | |
| 94 | --- |
| 95 | |
| 96 | ## Level 3: Wrap (Layout Slots) |
| 97 | |
| 98 | Inject content at specific positions in the layout without replacing built-in components. Override `Layout` in `theme/index.tsx`: |
| 99 | |
| 100 | ```tsx |
| 101 | // theme/index.tsx |
| 102 | import { Layout as OriginalLayout } from '@rspress/core/theme-original'; |
| 103 | export * from '@rspress/core/theme-original'; |
| 104 | |
| 105 | export function Layout() { |
| 106 | return ( |
| 107 | <OriginalLayout beforeNavTitle={<MyLogo />} bottom={<CustomFooter />} /> |
| 108 | ); |
| 109 | } |
| 110 | ``` |
| 111 | |
| 112 | Use runtime hooks inside slot components — import from `@rspress/core/runtime`: `useDark()`, `useLang()`, `useVersion()`, `usePage()`, `useSite()`, `useFrontmatter()`, `useI18n()`. |
| 113 | |
| 114 | > **All slots & examples**: Read `r |