Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ prebundle
preprocessors
psynch
pthread
rgba
rsbuild
rsdoctor
rslib
Expand Down
191 changes: 191 additions & 0 deletions skills/rspress-custom-theme/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
---
name: rspress-custom-theme
description: Customize 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 the default Rspress theme in any way. Also use when a user mentions theme/index.tsx, Layout slots, BEM class overrides, or rspress eject.
---

# Rspress Custom Theme

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.

## Workflow

1. **Understand the user's goal** — what do they want to change? (colors, layout, inject content, replace a component entirely?)
2. **Pick the right level** using the decision flow below
3. **Set up `theme/index.tsx`** if needed (Levels 1A, 3, 4 all need it)
4. **Implement** following the patterns in this skill and reference files
5. **Verify** the user's Rspress version is v2 (imports use `@rspress/core/*` not `rspress/*`)

## Decision Flow

| User wants to... | Level | Approach |
| ---------------------------------------------------------------- | ----- | --------------------------- |
| Change brand colors, fonts, spacing, shadows | 1 | CSS variables |
| Adjust a specific component's style (borders, padding, etc.) | 2 | BEM class overrides |
| Add content around existing components (banners, footers, logos) | 3 | Layout slots (wrap) |
| Override MDX rendering (custom `<h1>`, `<code>`, etc.) | 3 | `components` slot |
| Wrap the app in a provider (state, analytics, auth) | 4 | Eject `Root` |
| Completely replace a built-in component | 4 | Eject that component |
| Add a global floating component (back-to-top, chat widget) | — | `globalUIComponents` config |
| Control page layout structure (hide sidebar, blank page) | — | Frontmatter `pageType` |

---

## theme/index.tsx — The Entry Point

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:

```text
project/
├── docs/
├── theme/
│ ├── index.tsx # Theme entry — re-exports + overrides
│ ├── index.css # CSS variable / BEM overrides (optional)
│ └── components/ # Ejected components (Level 4)
└── rspress.config.ts
```

Minimal setup:

```tsx
// theme/index.tsx
import './index.css'; // optional
export * from '@rspress/core/theme-original';
```

**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.)

---

## Level 1: CSS Variables

Override CSS custom properties for brand colors, backgrounds, text, code blocks, and more.

**Option A** — `theme/index.css` (use when you also have component overrides in `theme/index.tsx`):

```css
/* theme/index.css */
:root {
--rp-c-brand: #7c3aed;
--rp-c-brand-light: #8b5cf6;
--rp-c-brand-dark: #6d28d9;
}
.dark {
--rp-c-brand: #a78bfa;
}
```

**Option B** — `globalStyles` (use when you only need CSS changes, no component overrides):

```ts
// rspress.config.ts
export default defineConfig({
globalStyles: path.join(__dirname, 'styles/custom.css'),
});
```

> **Full variable list**: Read `references/css-variables.md` for all available CSS variables with light/dark defaults.

---

## Level 2: BEM Class Overrides

All built-in components follow BEM naming: `.rp-[component]__[element]--[modifier]`.

Common targets: `.rp-nav`, `.rp-link`, `.rp-tabs`, `.rp-codeblock`, `.rp-codeblock__title`, `.rp-nav-menu__item--active`.

Use these in your CSS file for targeted style changes when CSS variables aren't granular enough.

---

## Level 3: Wrap (Layout Slots)

Inject content at specific positions in the layout without replacing built-in components. Override `Layout` in `theme/index.tsx`:

```tsx
// theme/index.tsx
import { Layout as OriginalLayout } from '@rspress/core/theme-original';
export * from '@rspress/core/theme-original';

export function Layout() {
return (
<OriginalLayout beforeNavTitle={<MyLogo />} bottom={<CustomFooter />} />
);
}
```

Use runtime hooks inside slot components — import from `@rspress/core/runtime`: `useDark()`, `useLang()`, `useVersion()`, `usePage()`, `useSite()`, `useFrontmatter()`, `useI18n()`.

> **All slots & examples**: Read `references/layout-slots.md` for the complete slot list and usage patterns including i18n and MDX component overrides.

---

## Level 4: Eject

Copy a built-in component's source for full replacement. Only use when wrap/slots cannot achieve the customization.

```bash
rspress eject # list available components
rspress eject DocFooter # eject to theme/components/DocFooter/
```

Then re-export in `theme/index.tsx` (named export takes precedence over the wildcard):

```tsx
export * from '@rspress/core/theme-original';
export { DocFooter } from './components/DocFooter';
```

> **Component list & patterns**: Read `references/eject-components.md` for available components, workflow, and common patterns.

---

## Global UI Components

For components that should render on every page without theme overrides:

```ts
// rspress.config.ts
export default defineConfig({
globalUIComponents: [
path.join(__dirname, 'components', 'BackToTop.tsx'),
[
path.join(__dirname, 'components', 'Analytics.tsx'),
{ trackingId: '...' },
],
],
});
```

---

## Page Types

Control layout per page via frontmatter `pageType`:

| Value | Description |
| ---------- | ------------------------------------- |
| `home` | Home page with navbar |
| `doc` | Standard doc with sidebar and outline |
| `doc-wide` | Doc without sidebar/outline |
| `custom` | Custom content with navbar only |
| `blank` | Custom content without navbar |
| `404` | 404 error page |

Fine-grained: set `navbar: false`, `sidebar: false`, `outline: false`, `footer: false` individually.

---

## Common Pitfalls

- **Circular import**: Using `@rspress/core/theme` instead of `@rspress/core/theme-original` in `theme/` files — causes infinite loop.
- **Eject over-use**: Ejecting when a Layout slot or CSS variable would suffice — creates upgrade burden.
- **Missing re-export**: Forgetting `export * from '@rspress/core/theme-original'` in `theme/index.tsx` — breaks all un-overridden components.
- **v1 imports**: Using `rspress/theme` or `@rspress/theme-default` — these are v1 paths. v2 uses `@rspress/core/theme-original`.

## Reference

- Custom theme guide: <https://rspress.rs/guide/basic/custom-theme>
- CSS variables: <https://rspress.rs/ui/vars>
- Layout component: <https://rspress.rs/ui/layout-components/layout>
- Built-in hooks: <https://rspress.rs/ui/hooks/>
- CLI commands (eject): <https://rspress.rs/api/commands>
143 changes: 143 additions & 0 deletions skills/rspress-custom-theme/references/css-variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# CSS Variables Reference

Complete list of CSS variables exposed by Rspress for theme customization. Override these in `theme/index.css` or via `globalStyles` in `rspress.config.ts`.

For dark mode overrides, wrap variables in `.dark { ... }`.

Official reference: <https://rspress.rs/ui/vars>

---

## Brand Colors

| Variable | Light Default | Dark Default |
| ---------------------- | --------------------------- | ------------ |
| `--rp-c-brand` | `#0095ff` | (same) |
| `--rp-c-brand-light` | `#33adff` | (same) |
| `--rp-c-brand-lighter` | `#c6e0fd` | (same) |
| `--rp-c-brand-dark` | `#0077ff` | (same) |
| `--rp-c-brand-darker` | `#005fcc` | (same) |
| `--rp-c-brand-tint` | `rgba(127, 163, 255, 0.16)` | (same) |

## Background

| Variable | Light Default | Dark Default |
| ---------------- | ------------- | ------------ |
| `--rp-c-bg` | `#ffffff` | `#121212` |
| `--rp-c-bg-soft` | `#f8f8f9` | `#292e37` |
| `--rp-c-bg-mute` | `#f1f1f1` | `#343a46` |
| `--rp-c-bg-alt` | `#fff` | `#000` |

## Text

| Variable | Light Default | Dark Default |
| ------------------------- | --------------------------- | --------------------------- |
| `--rp-c-text-0` | `#000000` | `#ffffff` |
| `--rp-c-text-1` | `#242424` | `rgba(255, 255, 245, 0.93)` |
| `--rp-c-text-2` | `rgba(0, 0, 0, 0.7)` | `rgba(255, 255, 245, 0.65)` |
| `--rp-c-text-3` | `rgba(60, 60, 60, 0.33)` | `rgba(235, 235, 235, 0.38)` |
| `--rp-c-text-4` | `rgba(60, 60, 60, 0.18)` | `rgba(235, 235, 235, 0.18)` |
| `--rp-c-text-code` | `#476582` | `#c9def1` |
| `--rp-c-text-code-bg` | `rgba(153, 161, 179, 0.08)` | `rgba(255, 255, 255, 0.08)` |
| `--rp-c-text-code-border` | `rgba(0, 0, 0, 0.05)` | `rgba(255, 255, 255, 0.05)` |
| `--rp-c-link` | `var(--rp-c-brand-dark)` | `var(--rp-c-brand-light)` |

## Dividers

| Variable | Light Default | Dark Default |
| ---------------------- | --------------------- | ------------------------ |
| `--rp-c-divider` | `rgba(0, 0, 0, 0.25)` | `rgba(84, 84, 84, 0.65)` |
| `--rp-c-divider-light` | `rgba(0, 0, 0, 0.12)` | `rgba(84, 84, 84, 0.48)` |

## Gray Scale

| Variable | Default |
| --------------------- | --------- |
| `--rp-c-gray` | `#8e8e8e` |
| `--rp-c-gray-light-1` | `#aeaeae` |
| `--rp-c-gray-light-2` | `#c7c7c7` |
| `--rp-c-gray-light-3` | `#d1d1d1` |
| `--rp-c-gray-light-4` | `#e5e5e5` |
| `--rp-c-gray-light-5` | `#f2f2f2` |

## Layout (Radius & Shadows)

| Variable | Default |
| --------------------------------------- | ---------------------- |
| `--rp-radius` | `1rem` |
| `--rp-radius-small` | `0.5rem` |
| `--rp-radius-large` | `1.5rem` |
| `--rp-shadow-1` through `--rp-shadow-5` | 5 levels of box-shadow |

## Code Block

| Variable | Light Default | Dark Default |
| ------------------------ | ------------------------------------- | -------------------- |
| `--rp-code-font-size` | `0.875rem` | (same) |
| `--rp-code-title-bg` | `#f8f8f9` | `#191919` |
| `--rp-code-block-color` | `rgb(46, 52, 64)` | `rgb(229, 231, 235)` |
| `--rp-code-block-bg` | `var(--rp-c-bg)` | (same) |
| `--rp-code-block-border` | `1px solid var(--rp-c-divider-light)` | (same) |
| `--rp-code-block-shadow` | `none` | (same) |

## Shiki Syntax Highlighting

### Light Mode

| Variable | Default |
| --------------------------------- | -------------------- |
| `--shiki-foreground` | `inherit` |
| `--shiki-background` | `transparent` |
| `--shiki-token-constant` | `#1976d2` |
| `--shiki-token-string` | `#31a94d` |
| `--shiki-token-comment` | `rgb(182, 180, 180)` |
| `--shiki-token-keyword` | `#cf2727` |
| `--shiki-token-parameter` | `#f59403` |
| `--shiki-token-function` | `#7041c8` |
| `--shiki-token-string-expression` | `#218438` |
| `--shiki-token-punctuation` | `#242323` |
| `--shiki-token-link` | `#22863a` |
| `--shiki-token-deleted` | `#d32828` |
| `--shiki-token-inserted` | `#22863a` |

### Dark Mode

| Variable | Default |
| --------------------------------- | --------- |
| `--shiki-token-constant` | `#6fb0fa` |
| `--shiki-token-string` | `#f9a86e` |
| `--shiki-token-comment` | `#6a727b` |
| `--shiki-token-keyword` | `#f47481` |
| `--shiki-token-parameter` | `#ff9800` |
| `--shiki-token-function` | `#ae8eeb` |
| `--shiki-token-string-expression` | `#4fb74d` |
| `--shiki-token-punctuation` | `#bbbbbb` |
| `--shiki-token-link` | `#f9a76d` |
| `--shiki-token-deleted` | `#ee6d7a` |
| `--shiki-token-inserted` | `#36c47f` |

## Home Page

| Variable | Light Default | Dark Default |
| -------------------------------- | ------------------------------------------ | ----------------------------------------------- |
| `--rp-home-hero-secondary-color` | `#a673ff` | (same) |
| `--rp-home-hero-title-color` | `transparent` | (same) |
| `--rp-home-hero-title-bg` | gradient (90deg) | (same) |
| `--rp-home-background-bg` | radial gradients | dark radial gradients |
| `--rp-home-feature-bg` | `linear-gradient(135deg, #fff, #f9f9f980)` | `linear-gradient(135deg, #ffffff00, #ffffff08)` |

## Quick Start Example

```css
/* theme/index.css */
:root {
--rp-c-brand: #7c3aed;
--rp-c-brand-light: #8b5cf6;
--rp-c-brand-dark: #6d28d9;
}
.dark {
--rp-c-brand: #a78bfa;
--rp-c-brand-light: #c4b5fd;
--rp-c-brand-dark: #8b5cf6;
}
```
Loading