feat: add skills
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
---
|
||||
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` |
|
||||
| Replace built-in icons (logo, GitHub, search, etc.) | — | Icon re-export |
|
||||
| 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.
|
||||
|
||||
---
|
||||
|
||||
## Custom Icons
|
||||
|
||||
Rspress has 27 built-in icons used across the UI. You can replace any of them by re-exporting your own icon component with the same name — no ejection needed. This uses the same `theme/index.tsx` mechanism: your named export takes precedence over the wildcard re-export.
|
||||
|
||||
**Icon type**: Each icon is a React component or a URL string:
|
||||
|
||||
```ts
|
||||
import type { FC, SVGProps } from 'react';
|
||||
type Icon = FC<SVGProps<SVGSVGElement>> | string;
|
||||
```
|
||||
|
||||
**Example 1** — Replace an icon with a custom SVG component:
|
||||
|
||||
```tsx
|
||||
// theme/index.tsx
|
||||
export * from '@rspress/core/theme-original';
|
||||
|
||||
// Named export overrides the wildcard — replaces the GitHub icon site-wide
|
||||
export const IconGithub = (props: React.SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...props}>
|
||||
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 ..." fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
```
|
||||
|
||||
**Example 2** — Use an SVGR import:
|
||||
|
||||
```tsx
|
||||
// theme/index.tsx
|
||||
export * from '@rspress/core/theme-original';
|
||||
|
||||
import CustomGithubIcon from './icons/github.svg?react';
|
||||
export const IconGithub = CustomGithubIcon;
|
||||
```
|
||||
|
||||
**Using `SvgWrapper` in MDX or custom components**:
|
||||
|
||||
```mdx
|
||||
import { SvgWrapper, IconGithub } from '@rspress/core/theme';
|
||||
|
||||
<SvgWrapper icon={IconGithub} width={24} height={24} />
|
||||
```
|
||||
|
||||
**Available icons**: `IconArrowDown`, `IconArrowRight`, `IconClose`, `IconCopy`, `IconDeprecated`, `IconDown`, `IconEdit`, `IconEmpty`, `IconExperimental`, `IconExternalLink`, `IconFile`, `IconGithub`, `IconGitlab`, `IconHeader`, `IconJump`, `IconLink`, `IconLoading`, `IconMenu`, `IconMoon`, `IconScrollToTop`, `IconSearch`, `IconSmallMenu`, `IconSuccess`, `IconSun`, `IconTitle`, `IconWrap`, `IconWrapped`.
|
||||
|
||||
> **Source**: See the [icons source](https://github.com/web-infra-dev/rspress/blob/main/packages/core/src/theme/icons.ts) for default implementations.
|
||||
|
||||
---
|
||||
|
||||
## 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 icons: <https://rspress.rs/ui/icons/>
|
||||
- Built-in hooks: <https://rspress.rs/ui/hooks/>
|
||||
- CLI commands (eject): <https://rspress.rs/api/commands>
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,154 @@
|
||||
# Eject Components Reference
|
||||
|
||||
Eject copies a built-in component's source code into your project for full customization. This is the heaviest approach — ejected components do not receive automatic updates when Rspress upgrades. Prefer CSS variables, BEM overrides, or Layout slots whenever possible.
|
||||
|
||||
Official reference: <https://rspress.rs/api/commands>
|
||||
|
||||
---
|
||||
|
||||
## Eject Command
|
||||
|
||||
```bash
|
||||
# List all available components
|
||||
rspress eject
|
||||
|
||||
# Eject a specific component
|
||||
rspress eject <ComponentName>
|
||||
```
|
||||
|
||||
Ejected source is placed in `theme/components/<ComponentName>/`.
|
||||
|
||||
## Available Components
|
||||
|
||||
| Component | Description | Consider wrapping first? |
|
||||
| ---------------- | ----------------------------------------- | ------------------------------------------------ |
|
||||
| `Layout` | Main layout container with all slot props | Yes — use Layout slots instead |
|
||||
| `Root` | Application root wrapper | Only eject for global providers |
|
||||
| `Banner` | Notification banner at top of page | Check `top` slot first |
|
||||
| `NavTitle` | Navigation logo and title | Check `navTitle` / `beforeNavTitle` slots |
|
||||
| `HomeLayout` | Complete home page layout | Check home page slots first |
|
||||
| `HomeHero` | Hero section on home page | Check `beforeHero` / `afterHero` slots |
|
||||
| `HomeFeature` | Feature grid cards | Check `beforeFeatures` / `afterFeatures` slots |
|
||||
| `HomeBackground` | Home page background effects | Try CSS variables first |
|
||||
| `HomeFooter` | Home page footer | Check `bottom` slot first |
|
||||
| `DocFooter` | Documentation page footer | Check `beforeDocFooter` / `afterDocFooter` slots |
|
||||
| `EditLink` | "Edit this page" link | Configure via `themeConfig.editLink` |
|
||||
| `LastUpdated` | Last updated timestamp | Usually config is enough |
|
||||
| `PrevNextPage` | Previous/next page navigation | Check `beforeDocFooter` slot |
|
||||
| `OverviewGroup` | Overview page group cards | — |
|
||||
| `Tag` | Tag/label component | — |
|
||||
|
||||
## Step-by-Step Eject Workflow
|
||||
|
||||
1. **Eject the component:**
|
||||
|
||||
```bash
|
||||
rspress eject DocFooter
|
||||
```
|
||||
|
||||
2. **Re-export in theme/index.tsx:**
|
||||
|
||||
```tsx
|
||||
// theme/index.tsx
|
||||
export * from '@rspress/core/theme-original';
|
||||
export { DocFooter } from './components/DocFooter';
|
||||
```
|
||||
|
||||
The named export takes precedence over the wildcard re-export, so Rspress uses your custom version.
|
||||
|
||||
3. **Modify the ejected source** in `theme/components/DocFooter/`.
|
||||
|
||||
## Common Pattern: Root for Global Providers
|
||||
|
||||
The most common eject use case is wrapping the entire app in a context provider (state management, analytics, auth, etc.):
|
||||
|
||||
```tsx
|
||||
// theme/components/Root/index.tsx
|
||||
import type { RootProps } from '@rspress/core/theme';
|
||||
|
||||
export function Root({ children }: RootProps) {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<AnalyticsProvider>{children}</AnalyticsProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
// theme/index.tsx
|
||||
export * from '@rspress/core/theme-original';
|
||||
export { Root } from './components/Root';
|
||||
```
|
||||
|
||||
## Common Pattern: Custom Home Page (HomeLayout)
|
||||
|
||||
When the default home page structure (Hero + Features) doesn't meet the design requirements — for example, you need a completely different landing page with custom sections, animations, or a non-standard layout — write a custom `HomeLayout` component and re-export it directly:
|
||||
|
||||
```tsx
|
||||
// theme/components/HomeLayout/index.tsx
|
||||
import { useSite, useLang } from '@rspress/core/runtime';
|
||||
|
||||
export function HomeLayout() {
|
||||
const site = useSite();
|
||||
const lang = useLang();
|
||||
const { title, description } = site.siteData;
|
||||
|
||||
return (
|
||||
<div className="custom-home">
|
||||
<section className="hero">
|
||||
<h1>{title}</h1>
|
||||
<p>{description}</p>
|
||||
<div className="hero-actions">
|
||||
<a
|
||||
href={lang === 'zh' ? '/zh/guide/start' : '/guide/start'}
|
||||
className="primary-btn"
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
<a href="https://github.com/..." className="secondary-btn">
|
||||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="showcase">
|
||||
{/* Custom content: testimonials, stats, demos, etc. */}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
// theme/index.tsx
|
||||
export * from '@rspress/core/theme-original';
|
||||
export { HomeLayout } from './components/HomeLayout';
|
||||
```
|
||||
|
||||
The named export overrides the built-in `HomeLayout` from the wildcard re-export — no need to eject first.
|
||||
|
||||
If you only need to add content before/after the Hero or Features sections (without replacing the entire home page), prefer Layout slots (`beforeHero`, `afterHero`, `beforeFeatures`, `afterFeatures`) instead — see `references/layout-slots.md`.
|
||||
|
||||
## Common Pattern: Custom Doc Footer
|
||||
|
||||
```tsx
|
||||
// theme/components/DocFooter/index.tsx
|
||||
import { useFrontmatter } from '@rspress/core/runtime';
|
||||
|
||||
export function DocFooter() {
|
||||
const frontmatter = useFrontmatter();
|
||||
return (
|
||||
<footer className="custom-doc-footer">
|
||||
{frontmatter.author && <span>Author: {frontmatter.author}</span>}
|
||||
<a href="https://github.com/...">Edit this page</a>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Always import from `@rspress/core/theme-original` in `theme/` files, never from `@rspress/core/theme` (the latter resolves to your own `theme/index.tsx`, causing circular imports).
|
||||
- After ejecting, you own that component. Track Rspress changelogs for upstream changes you might want to incorporate manually.
|
||||
- Run `rspress eject` (no args) to see the up-to-date list of available components — the list above may change between Rspress versions.
|
||||
@@ -0,0 +1,153 @@
|
||||
# Layout Slots Reference
|
||||
|
||||
The `Layout` component accepts slot props (`React.ReactNode`) for injecting content at specific positions without replacing built-in components. This is the recommended way to extend Rspress before considering eject.
|
||||
|
||||
Official reference: <https://rspress.rs/ui/layout-components/layout>
|
||||
|
||||
---
|
||||
|
||||
## All Available Slots
|
||||
|
||||
### Navigation Bar
|
||||
|
||||
| Slot | Position |
|
||||
| ---------------- | ------------------------------------ |
|
||||
| `beforeNav` | Before the entire navigation bar |
|
||||
| `afterNav` | After the entire navigation bar |
|
||||
| `beforeNavTitle` | Before the nav title/logo (top-left) |
|
||||
| `navTitle` | Replaces the nav title content |
|
||||
| `afterNavTitle` | After the nav title/logo |
|
||||
| `beforeNavMenu` | Before the nav menu items |
|
||||
| `afterNavMenu` | After the nav menu items |
|
||||
|
||||
### Sidebar & Outline
|
||||
|
||||
| Slot | Position |
|
||||
| --------------- | ----------------------------------- |
|
||||
| `beforeSidebar` | Above the left sidebar |
|
||||
| `afterSidebar` | Below the left sidebar |
|
||||
| `beforeOutline` | Above the right outline (TOC) panel |
|
||||
| `afterOutline` | Below the right outline panel |
|
||||
|
||||
### Home Page
|
||||
|
||||
| Slot | Position |
|
||||
| ---------------- | ------------------------ |
|
||||
| `beforeHero` | Before the Hero section |
|
||||
| `afterHero` | After the Hero section |
|
||||
| `beforeFeatures` | Before the Features grid |
|
||||
| `afterFeatures` | After the Features grid |
|
||||
|
||||
### Doc Page
|
||||
|
||||
| Slot | Position |
|
||||
| ------------------ | ------------------------------------- |
|
||||
| `beforeDoc` | At the very beginning of the doc page |
|
||||
| `afterDoc` | At the very end of the doc page |
|
||||
| `beforeDocContent` | Before the document content area |
|
||||
| `afterDocContent` | After the document content area |
|
||||
| `beforeDocFooter` | Before the doc footer (prev/next nav) |
|
||||
| `afterDocFooter` | After the doc footer |
|
||||
|
||||
### Global
|
||||
|
||||
| Slot | Position |
|
||||
| ------------ | ---------------------------------------------------------------------- |
|
||||
| `top` | At the very top of the entire page |
|
||||
| `bottom` | At the very bottom of the entire page |
|
||||
| `components` | Custom MDX component overrides (`Record<string, React.ComponentType>`) |
|
||||
|
||||
---
|
||||
|
||||
## Usage Pattern
|
||||
|
||||
All examples below follow the same structure in `theme/index.tsx`. The key parts:
|
||||
|
||||
- Import `Layout` from `@rspress/core/theme-original` (not `@rspress/core/theme` — that causes circular imports)
|
||||
- Re-export everything: `export * from '@rspress/core/theme-original'`
|
||||
- Export your custom `Layout` that wraps the original with slot props
|
||||
|
||||
### Basic — Single Slot
|
||||
|
||||
```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 />} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Slots
|
||||
|
||||
```tsx
|
||||
// theme/index.tsx
|
||||
import { Layout as OriginalLayout } from '@rspress/core/theme-original';
|
||||
export * from '@rspress/core/theme-original';
|
||||
|
||||
export function Layout() {
|
||||
return (
|
||||
<OriginalLayout
|
||||
top={<div className="announcement-bar">New version released!</div>}
|
||||
bottom={<footer>© 2025 My Company</footer>}
|
||||
afterOutline={<div>Related resources</div>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### With i18n Hooks
|
||||
|
||||
```tsx
|
||||
// theme/index.tsx
|
||||
import { Layout as OriginalLayout } from '@rspress/core/theme-original';
|
||||
import { useLang } from '@rspress/core/runtime';
|
||||
export * from '@rspress/core/theme-original';
|
||||
|
||||
function LocalizedBanner() {
|
||||
const lang = useLang();
|
||||
return <div>{lang === 'zh' ? '欢迎' : 'Welcome'}</div>;
|
||||
}
|
||||
|
||||
export function Layout() {
|
||||
return <OriginalLayout top={<LocalizedBanner />} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Override MDX Components
|
||||
|
||||
The `components` slot accepts a `Record<string, React.ComponentType>` to override how MDX elements render:
|
||||
|
||||
```tsx
|
||||
// theme/index.tsx
|
||||
import { Layout as OriginalLayout } from '@rspress/core/theme-original';
|
||||
export * from '@rspress/core/theme-original';
|
||||
|
||||
function CustomH1({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<h1 style={{ borderBottom: '2px solid var(--rp-c-brand)' }}>{children}</h1>
|
||||
);
|
||||
}
|
||||
|
||||
export function Layout() {
|
||||
return <OriginalLayout components={{ h1: CustomH1 }} />;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Hooks
|
||||
|
||||
Use these hooks inside slot components. Import from `@rspress/core/runtime`.
|
||||
|
||||
| Hook | Purpose |
|
||||
| ------------------ | ----------------------------------- |
|
||||
| `useDark()` | Returns whether dark mode is active |
|
||||
| `useLang()` | Returns current language code |
|
||||
| `useVersion()` | Returns current doc version |
|
||||
| `usePage()` | Returns current page metadata |
|
||||
| `usePages()` | Returns all pages metadata |
|
||||
| `useSite()` | Returns site-level configuration |
|
||||
| `useFrontmatter()` | Returns current page frontmatter |
|
||||
| `useI18n()` | Returns i18n translation function |
|
||||
Reference in New Issue
Block a user