diff --git a/.agents/skills/rspress-best-practices/SKILL.md b/.agents/skills/rspress-best-practices/SKILL.md new file mode 100644 index 0000000..af0370b --- /dev/null +++ b/.agents/skills/rspress-best-practices/SKILL.md @@ -0,0 +1,82 @@ +--- +name: rspress-best-practices +description: Rspress best practices for config, CLI workflow, content organization, frontmatter, MDX, themes, i18n, search, static assets, deployment, and debugging. Use when writing, reviewing, or troubleshooting Rspress documentation sites. +--- + +# Rspress Best Practices + +Apply these rules when writing or reviewing Rspress (v2) sites. + +## Configuration + +- Use `rspress.config.ts` and `defineConfig` from `@rspress/core` +- Set `root` explicitly when docs are not under the default `docs/` directory +- Keep site-wide settings such as `title`, `description`, `icon`, `logo`, `base`, and `lang` in config instead of repeating them in page files +- Prefer first-class Rspress options before custom theme code or low-level bundler overrides +- Keep custom theme code in a top-level `theme/` directory and import original theme pieces from `@rspress/core/theme-original` + +## CLI + +- Use `rspress dev` for local development +- Use `rspress build` for production output +- Use `rspress preview` only for local preview of the built site +- Use `rspress eject` only when CSS variables, class overrides, or layout wrapping cannot solve the customization + +## Docs Structure And Navigation + +- Keep docs content under one clear docs root and group pages by topic or workflow, not by team ownership +- Use `_meta.json` or `_nav.json` to control sidebar and navigation labels/order instead of encoding order in filenames +- Put reusable MDX snippets or shared components in shared files instead of duplicating them across pages +- Keep landing pages concise and link to deeper task-oriented guides from them + +## Writing And Frontmatter + +- Add clear `title` and `description` frontmatter, and set `sidebar`, `outline`, `navbar`, or `footer` only when page defaults are not enough +- Use `pageType: home`, `doc`, `doc-wide`, `custom`, or `blank` intentionally based on layout needs +- Write task-first headings and short intros; avoid marketing-heavy copy in technical docs +- Prefer one topic per page and split overly long pages by workflow or feature area +- Keep code examples minimal, runnable, and version-accurate + +## MDX And Components + +- Use MDX for interactive docs and embedded components, but keep the main narrative understandable as plain markdown +- Prefer documented Rspress theme/runtime APIs over importing from internal source paths +- For app-wide UI or providers, use `globalUIComponents` or theme overrides instead of repeating imports in each page + +## Theme And Styling + +- Prefer CSS variables for brand colors, spacing, and surface styling +- Prefer BEM class overrides or `Layout` slots before ejecting built-in components +- In `theme/` files, keep `export * from '@rspress/core/theme-original'` unless intentionally replacing a named export +- Avoid full component ejection unless config, CSS, and wrapping cannot meet the requirement + +## I18n, Search, And AI + +- For multilingual sites, organize locale content under per-language directories and keep navigation mirrored where practical +- Keep descriptions and other frontmatter text in the same language as the page content +- Configure search intentionally: use local search for small or medium sites, and hosted search when scale or cross-version indexing requires it +- Enable `llms` or `ssgMd` only when the site benefits from machine-readable outputs, and keep descriptions accurate because those outputs surface page summaries + +## Assets And Public Files + +- Import source-managed images and components from docs/theme source when they belong to the content +- Use `public/` only for assets that must keep stable URL paths, such as favicons, social images, or download files +- Reference public assets by absolute site path and make sure they still work when `base` is set + +## Plugins And Integration + +- Prefer official Rspress plugins for search, preview, and API-doc scenarios before building custom solutions +- For component or library docs, use `@rspress/plugin-preview` and `@rspress/plugin-api-docgen` when interactive demos or API tables are needed +- Keep plugin usage explicit in config and remove unused plugins to reduce maintenance cost + +## Build, Deploy, And Debugging + +- Validate both `rspress dev` and `rspress build`; a page that works in dev can still fail during static generation +- Verify broken links, missing assets, and wrong `base` handling before deployment +- Keep generated output out of source control unless the hosting workflow explicitly requires committed artifacts +- When debugging content issues, inspect the resolved docs root, frontmatter, and theme overrides before assuming a bundler problem + +## Documentation + +- For the latest Rspress docs, read https://rspress.rs/llms.txt +- Use the config and API docs when checking exact option names or current behavior diff --git a/.agents/skills/rspress-custom-theme/SKILL.md b/.agents/skills/rspress-custom-theme/SKILL.md new file mode 100644 index 0000000..2ee763c --- /dev/null +++ b/.agents/skills/rspress-custom-theme/SKILL.md @@ -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 `
`, 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 (
+ } bottom={ } />
+ );
+}
+```
+
+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> | 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) => (
+
+);
+```
+
+**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';
+
+
+```
+
+**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:
+- CSS variables:
+- Layout component:
+- Built-in icons:
+- Built-in hooks:
+- CLI commands (eject):
diff --git a/.agents/skills/rspress-custom-theme/references/css-variables.md b/.agents/skills/rspress-custom-theme/references/css-variables.md
new file mode 100644
index 0000000..3fca55f
--- /dev/null
+++ b/.agents/skills/rspress-custom-theme/references/css-variables.md
@@ -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:
+
+---
+
+## 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;
+}
+```
diff --git a/.agents/skills/rspress-custom-theme/references/eject-components.md b/.agents/skills/rspress-custom-theme/references/eject-components.md
new file mode 100644
index 0000000..235800f
--- /dev/null
+++ b/.agents/skills/rspress-custom-theme/references/eject-components.md
@@ -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:
+
+---
+
+## Eject Command
+
+```bash
+# List all available components
+rspress eject
+
+# Eject a specific component
+rspress eject
+```
+
+Ejected source is placed in `theme/components//`.
+
+## 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 (
+
+ {children}
+
+ );
+}
+```
+
+```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 (
+
+
+ {title}
+ {description}
+
+
+ Get Started
+
+
+ GitHub
+
+
+
+
+
+ {/* Custom content: testimonials, stats, demos, etc. */}
+
+
+ );
+}
+```
+
+```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 (
+
+ );
+}
+```
+
+## 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.
diff --git a/.agents/skills/rspress-custom-theme/references/layout-slots.md b/.agents/skills/rspress-custom-theme/references/layout-slots.md
new file mode 100644
index 0000000..1774218
--- /dev/null
+++ b/.agents/skills/rspress-custom-theme/references/layout-slots.md
@@ -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:
+
+---
+
+## 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`) |
+
+---
+
+## 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 } />;
+}
+```
+
+### 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 (
+ New version released!}
+ bottom={}
+ afterOutline={Related resources}
+ />
+ );
+}
+```
+
+### 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 {lang === 'zh' ? '欢迎' : 'Welcome'};
+}
+
+export function Layout() {
+ return } />;
+}
+```
+
+### Override MDX Components
+
+The `components` slot accepts a `Record` 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 (
+ {children}
+ );
+}
+
+export function Layout() {
+ return ;
+}
+```
+
+---
+
+## 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 |
diff --git a/.claude/skills/rspress-best-practices b/.claude/skills/rspress-best-practices
new file mode 120000
index 0000000..0785fd5
--- /dev/null
+++ b/.claude/skills/rspress-best-practices
@@ -0,0 +1 @@
+../../.agents/skills/rspress-best-practices
\ No newline at end of file
diff --git a/.claude/skills/rspress-custom-theme b/.claude/skills/rspress-custom-theme
new file mode 120000
index 0000000..5e83813
--- /dev/null
+++ b/.claude/skills/rspress-custom-theme
@@ -0,0 +1 @@
+../../.agents/skills/rspress-custom-theme
\ No newline at end of file
diff --git a/skills-lock.json b/skills-lock.json
new file mode 100644
index 0000000..912e464
--- /dev/null
+++ b/skills-lock.json
@@ -0,0 +1,17 @@
+{
+ "version": 1,
+ "skills": {
+ "rspress-best-practices": {
+ "source": "rstackjs/agent-skills",
+ "sourceType": "github",
+ "skillPath": "skills/rspress-best-practices/SKILL.md",
+ "computedHash": "4e7ae99820cbe318bbaee92aa8b5ba115c41ea6727d4d109e1133e45a2b43368"
+ },
+ "rspress-custom-theme": {
+ "source": "rstackjs/agent-skills",
+ "sourceType": "github",
+ "skillPath": "skills/rspress-custom-theme/SKILL.md",
+ "computedHash": "7fe2fa67caabd3336adc635fbe97716d14ff22780d0046237d3b5086e8565277"
+ }
+ }
+}