dioxus-docs-kit 0.7.1

Reusable documentation site shell for Dioxus applications
Documentation

dioxus-docs-kit

Reusable documentation site kit for Dioxus applications.

Drop-in layout with sidebar navigation, full-text search, page navigation, OpenAPI API reference pages, mobile drawer, and theme toggle. Built on dioxus-mdx for content rendering.

Quick Start

Add to your Cargo.toml:

[dependencies]
dioxus-docs-kit = "0.7"

1. Build a Registry

The DocsRegistry holds all parsed content, the navigation tree, the search index, and any OpenAPI specs:

use dioxus_docs_kit::{DocsConfig, DocsRegistry};
use std::sync::LazyLock;

static DOCS: LazyLock<DocsRegistry> = LazyLock::new(|| {
    DocsConfig::new(
        include_str!("../docs/_nav.json"),
        doc_content_map(), // HashMap<&'static str, &'static str> generated by build.rs
    )
    .with_openapi("api-reference", include_str!("../docs/api-reference/spec.yaml"))
    .build()
});

2. Wire Into Your Router

Create a thin layout wrapper that provides the DocsContext and registry to the library components. The use_docs_providers hook bundles all the context setup into one call:

use dioxus::prelude::*;
use dioxus_docs_kit::{DocsLayout, use_docs_context, use_docs_providers};

#[component]
fn MyDocsLayout() -> Element {
    let nav = use_navigator();
    let route = use_route::<Route>();

    // A plain String — extract the slug from your route, e.g. slug.join("/").
    let current_path = /* ... */;

    // `use_docs_context` rewraps the path reactively for you. Building the
    // signal yourself with a plain `use_memo(move || ...)` captures the first
    // route and never updates — the sidebar highlight would freeze.
    let docs_ctx = use_docs_context(
        current_path,
        "/docs",
        Callback::new(move |path: String| {
            nav.push(/* build route from path */);
        }),
    )
    .with_site_url("https://your-site.com"); // optional: canonical/OG URLs

    let providers = use_docs_providers(&DOCS, docs_ctx);
    // providers.search_open / providers.drawer_open are available for
    // wiring a custom header.

    rsx! {
        DocsLayout {
            Outlet::<Route> {}
        }
    }
}

3. Add Routes

#[derive(Routable, Clone, PartialEq)]
enum Route {
    #[layout(MyDocsLayout)]
        #[route("/docs")]
        DocsIndex {},
        #[route("/docs/:..slug")]
        DocsPage { slug: Vec<String> },
}

That's it. The library handles sidebar rendering, search, page content, previous/next navigation, and mobile responsiveness.

Components

Component Description
DocsLayout Full page layout with sidebar, content area, and table of contents
DocsSidebar Navigation sidebar built from _nav.json
DocsPageContent Renders MDX docs or OpenAPI endpoint pages
DocsPageNav Previous/next page navigation
SearchModal Full-text search across all docs
SearchButton Trigger button for the search modal
MobileDrawer Mobile navigation drawer
ThemeToggle Light/dark theme switcher

Navigation Config

Define your docs structure in _nav.json. tabs is a flat list of tab names, and each group optionally names the tab it belongs to:

{
  "tabs": ["Docs", "Guides"],
  "groups": [
    {
      "group": "Getting Started",
      "tab": "Docs",
      "pages": [
        "getting-started/introduction",
        "getting-started/quickstart"
      ]
    }
  ]
}

Content Pipeline

All doc content is embedded at compile time via include_str!(). A typical build.rs reads _nav.json, collects all referenced .mdx files, and generates a HashMap<&'static str, &'static str> mapping paths to content.

See the example project for a complete build.rs implementation.

Styling Setup

Zero-setup: the precompiled stylesheet

The crate ships a compiled stylesheet (DOCS_KIT_CSS) covering every class its components emit — Tailwind utilities, DaisyUI dark/light themes, typography prose, and the --dk-* theming tokens. Link it and skip the rest of this section — no Tailwind, no Bun, no safelist:

rsx! {
    document::Stylesheet { href: dioxus_docs_kit::DOCS_KIT_CSS }
}

It only contains the kit's classes; if your own pages use Tailwind utilities the kit doesn't, run your own build instead. That path requires Tailwind CSS 4, DaisyUI 5, and @tailwindcss/typography:

Install dependencies

bun add tailwindcss @tailwindcss/typography daisyui

Configure Tailwind

Add to your tailwind.css:

@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "daisyui" {
    themes: dark --default, light;
}

@source "./src/**/*.{rs,html,css}";

Include dioxus-docs-kit classes

When using as a crates.io dependency, Tailwind can't scan the crate source (it lives in ~/.cargo with machine-specific paths). Copy safelist.html from the crate into your project root and add it as a source:

@source "./safelist.html";

The safelist includes all classes from both dioxus-docs-kit and dioxus-mdx, including dynamic runtime classes that Tailwind cannot detect from source scanning alone.

When using as a workspace path dependency, you can point directly at the source instead:

@source "./crates/dioxus-docs-kit/src/**/*.rs";
@source "./crates/dioxus-mdx/src/**/*.rs";

Theming

The kit exposes a public theming surface so consumers can restyle without forking or fighting DaisyUI internals.

Public CSS variables

Copy theme.css from the crate into your project and import it alongside Tailwind:

@import "tailwindcss";
@import "./theme.css";

All tokens have DaisyUI fallbacks, so DaisyUI users inherit the current theme; non-DaisyUI users get a sensible neutral default. Override any of them:

.my-site .dk-root {
  --dk-accent: #f0a57c;
  --dk-font-heading: 'Instrument Serif', serif;
  --dk-radius-lg: 20px;
}
Token Purpose
--dk-bg, --dk-bg-sub, --dk-bg-alt Surface colors
--dk-fg, --dk-muted, --dk-dim Foreground / text colors
--dk-border Border color
--dk-accent, --dk-accent-fg, --dk-accent-soft Accent
--dk-radius-sm, --dk-radius, --dk-radius-lg Corner radii
--dk-font-body, --dk-font-heading, --dk-font-mono Typography
--dk-article-width, --dk-sidebar-width, --dk-toc-width Layout widths

Stable dk-* classes

Structural nodes carry semver-stable dk-* class names. Target these in your own CSS instead of DaisyUI or internal classes:

Class What it wraps
dk-root, dk-docs-root Outermost docs wrapper
dk-header, dk-shell, dk-sidebar, dk-main, dk-toc Layout regions
dk-tabs, dk-tab, dk-tab-active Tab bar
dk-nav, dk-nav-group, dk-nav-group-title, dk-nav-item, dk-nav-item-active Sidebar navigation
dk-article, dk-article-header, dk-article-title, dk-article-description, dk-article-body Article regions
dk-pagination, dk-page-prev, dk-page-next Previous/next links
dk-search-trigger, dk-search-dialog, dk-search-input, dk-search-results, dk-search-result Search
dk-drawer Mobile drawer

Slots on DocsLayout

DocsLayout accepts optional element slots. Each slot is wrapped in a dk-*-slot class for CSS hooks.

DocsLayout {
    announcement_bar: Some(rsx!{ AnnouncementBar {} }),
    sidebar_header: Some(rsx!{ MyProductSwitcher {} }),
    sidebar_footer: Some(rsx!{ EditOnGitHub {} }),
    footer: Some(rsx!{ SiteFooter {} }),
    Outlet::<Route> {}
}

DocsPageContent additionally accepts an article_footer slot (rendered below the article body, before pagination) — useful for "Was this helpful?" widgets.

Density variants

DocsLayout accepts a variant prop for two built-in density presets:

DocsLayout { variant: DocsVariant::Reference, Outlet::<Route> {} }
Variant Feel --dk-article-width
Prose (default) Wide margins, serif-friendly, long-form reading 72ch
Reference Tighter column, smaller type, denser headings 64ch

The variant is emitted as a class on dk-root (dk-variant-prose or dk-variant-reference) so consumers can layer further tweaks.

Pre-built theme examples

examples/themes/ ships three drop-in visual identities built entirely on top of --dk-* tokens:

  • warm-editorial.css — amber accent, serif headings, cream surfaces
  • brutalist-light.css — high contrast, zero-radius, monospace
  • default.css — baseline (nothing overridden)

See THEMING.md for the full roadmap and proposals open for community contribution.

Features

  • web (default) — enables web-specific features (propagated to dioxus-mdx)
  • mermaid (default) — renders ```mermaid fences as diagrams
  • highlight (default) — syntax-highlights code blocks via dioxus-code. Disable (default-features = false) to drop the dependency and its C-compiling tree-sitter grammars: no C toolchain (or wasm stderr shim) is needed and the binary is smaller, but code blocks render as plain (uncolored) text. Turning it off also removes the dioxus-code re-exports and DocsConfig::with_code_theme[s].
  • lang-* — one tree-sitter grammar each, on top of highlight. Enabled by default: lang-bash, lang-css, lang-dockerfile, lang-html, lang-javascript, lang-json, lang-markdown, lang-python, lang-toml, lang-typescript, lang-yaml. Also available: lang-rust (a no-op — Rust always highlights), lang-c-sharp, lang-cpp, lang-tsx. Any other language goes through dioxus-code directly; see Syntax Highlighting.
  • openapi (default) — renders API reference pages from OpenAPI specs. Required by DocsConfig::with_openapi(), which is absent without it. Disable (default-features = false) to drop openapiv3 and serde_yaml (and its unsafe-libyaml) from the build; docs pages, blog and frontmatter are unaffected.
  • server — Axum route builders for crawler-facing endpoints

With the server feature, SeoRouter generates per-page raw-Markdown routes, llms.txt / llms-full.txt, sitemaps, blog RSS, and robots.txt as plain Axum routes (server functions would JSON-encode the bodies):

use dioxus_docs_kit::server::SeoRouter;

dioxus::server::serve(|| async {
    let seo = SeoRouter::new("https://your-site.com", "My Docs", "Documentation")
        .with_docs(&DOCS, "/docs")
        .into_router();
    Ok(dioxus::server::router(App).merge(seo))
});

Syntax Highlighting

Code blocks render through dioxus-code. Every language is a separate tree-sitter grammar compiled into your binary, so each one sits behind its own lang-* feature. Enabled by default:

bash, css, dockerfile, html, javascript, json, markdown, python, rust, toml, typescript, yaml

Rust needs no feature of its own — dioxus-code's runtime always compiles it, so highlight alone highlights Rust.

lang-c-sharp, lang-cpp and lang-tsx exist too but are deliberately off by default: dropping the C# and C++ grammars cut this repo's own release wasm from 19.0 MB to 9.9 MB (data section 15.2 MB to 6.3 MB), and TSX (1.5 MB, React-only) followed. Turn them back on if your docs fence those languages:

[dependencies]
dioxus-docs-kit = { version = "0.7", features = ["lang-c-sharp", "lang-cpp", "lang-tsx"] }

To trim the set down to what your docs actually fence:

[dependencies]
dioxus-docs-kit = { version = "0.7", default-features = false, features = [
    "web", "mermaid", "highlight", "openapi", "lang-bash", "lang-json", "lang-toml",
] }

Any other language

The kit carries lang-* features only for the languages above. For anything else dioxus-code supports — Go, Zig, Kotlin, SQL, and ~90 more — add dioxus-code to your own Cargo.toml with the grammar you want. Cargo unifies that feature into the same dioxus-code the kit already depends on, so Language::from_slug (which the kit uses to resolve a fence) is compiled with the union of the features and picks the grammar up. No kit changes needed:

[dependencies]
dioxus-docs-kit = "0.7"
dioxus-code = { version = "0.1", default-features = false, features = ["runtime", "lang-go"] }

That route matches on dioxus-code's canonical language slug, so use it in the fence (```go). The friendly aliases (```c++, ```yml, ```sh) only exist for the languages the kit has its own feature for. See the dioxus-code feature list for every available flag.

A fence whose grammar is not compiled into the build renders as plain, uncolored text in the same markup — it does not fail or fall back to an unrelated grammar.

Production build

Bundle with debug symbols off:

dx bundle --web --release --debug-symbols false

dx keeps DWARF by default and wasm-opt aborts on it ("compile unit size was incorrect"), after which dx silently ships the unoptimized wasm-bindgen output — for this repo's own site that was 25 MB instead of 7 MB. Two more things worth copying from this repo's root Cargo.toml and CI workflow:

  • [profile.wasm-release] / [profile.server-release] — the profiles dx actually builds with (opt-level = "z", fat LTO, panic = "abort" for the wasm; strip = true for the server).
  • Brotli sidecars: write a .br next to every .wasm/.js/.css under the bundle's public/ directory (brotli -q 11 -k). dioxus-server serves them automatically with content-encoding: br, taking the wasm from ~7 MB to ~1.4 MB on the wire. scripts/wasm-size.sh in this repo reports both numbers and can gate CI on the raw size.

License

MIT