Overview
If your project ships agent skills as skills/<name>/SKILL.md, the kit can turn each one into a page: name, description, a copyable npx skills add command, links to the source on GitHub, then the rendered skill body. Pages are listed in a sitemap and in llms.txt, so they are found by search engines and agents alike. See this site's skills for a live example.
The _skills.json Manifest
Put a _skills.json next to the skill directories:
skills/
├── _skills.json
├── my-cli-setup/
│ └── SKILL.md
└── my-cli-theming/
└── SKILL.md{
"repo": "acme/my-cli",
"path": "skills",
"ref": "main",
"groups": [
{ "group": "Setup", "skills": ["my-cli-setup"] },
{ "group": "Theming", "skills": ["my-cli-theming"] }
]
}groupssets the sidebar and index order. Each entry is a skill directory name.repo,pathandrefare where the install command and the GitHub links point.refis optional and defaults tomain.
Each SKILL.md needs name (equal to its directory name) and description in its frontmatter. Other keys such as license, allowed-tools or metadata are ignored.
Build Script
Add SkillsBuild to build.rs, next to DocsBuild:
dioxus_docs_kit_build::SkillsBuild::new("skills/_skills.json")
.with_validation(dioxus_docs_kit_build::ValidationMode::Strict)
.generate();In strict mode a missing SKILL.md or a name that differs from its directory fails the build. A missing description is only a warning.
Registry and Routes
use dioxus_docs_kit::{
SkillPage, SkillsConfig, SkillsContext, SkillsIndex, SkillsLayout, SkillsRegistry,
use_skills_providers,
};
static SKILLS: LazyLock<SkillsRegistry> = LazyLock::new(|| {
SkillsConfig::new(dioxus_docs_kit::skills_bundle!())
.with_base_path("/skills")
.build()
});
#[derive(Debug, Clone, Routable, PartialEq)]
enum Route {
// ...
#[layout(MySkillsLayout)]
#[route("/skills")]
SkillsIndexPage {},
#[route("/skills/:slug")]
SkillDetailPage { slug: String },
}
#[component]
fn MySkillsLayout() -> Element {
let nav = use_navigator();
let route = use_route::<Route>();
// `use_reactive!` keeps the sidebar highlight in sync with navigation.
let current_slug = use_memo(use_reactive!(|route| match route {
Route::SkillDetailPage { slug } => slug,
_ => String::new(),
}));
let ctx = SkillsContext::new(
current_slug,
SKILLS.base_path(),
Callback::new(move |slug: String| {
nav.push(Route::SkillDetailPage { slug });
}),
)
.with_site_url("https://example.com");
let providers = use_skills_providers(&SKILLS, ctx);
rsx! {
SkillsLayout {
// Wire `providers.search_open` to a `SkillsSearchButton` and
// `providers.drawer_open` to a menu button shown below `lg`.
// Omit `header` for a default bar with both.
header: rsx! {
MyNavbar {
search_open: providers.search_open,
drawer_open: providers.drawer_open,
}
},
Outlet::<Route> {}
}
}
}
#[component]
fn SkillsIndexPage() -> Element {
rsx! { SkillsIndex { description: "Agent skills for My CLI." } }
}
#[component]
fn SkillDetailPage(slug: String) -> Element {
rsx! { SkillPage { slug } }
}SkillsLayout brings a slide-in sidebar drawer for small screens and a skills search modal (Cmd/Ctrl+K). Search is section-level: a match inside a heading's section jumps to that heading.
With the server feature, add the skills to the SeoRouter for a /sitemap-skills.xml, a ## Skills section in llms.txt and a raw <base>/<slug>.md per skill:
SeoRouter::new(SITE_URL, SITE_TITLE, SITE_DESC)
.with_docs(&DOCS, "/docs")
.with_skills(&SKILLS, SKILLS.base_path())
.into_router()Skills from Another Repo
Skills often live in a CLI's repo while the docs site lives elsewhere. Vendor them: copy skills/**/SKILL.md into the docs repo with a scheduled GitHub Action that opens a PR, and point repo/path at the source repo. The pages are built from the copies; the install command and links go to the original.
# .github/workflows/sync-skills.yml (in the docs repo)
on:
schedule: [{ cron: "0 6 * * 1" }]
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with: { repository: acme/my-cli, path: upstream }
- run: |
(cd upstream/skills && find . -name SKILL.md -exec cp --parents {} ../../skills/ \;)
rm -rf upstream
- uses: peter-evans/create-pull-request@v6
with:
title: "docs: sync skills from acme/my-cli"
branch: sync-skills_skills.json stays hand-maintained in the docs repo, so a new upstream skill shows up only once you add it to a group.