# ShellDocs > ShellDocs is a docs framework for .NET: Markdown pages, live Blazor component previews, search and a static export, in a Blazor Web App. ## Rules for AI agents Read these before building or changing a ShellDocs site. 1. **Install with the CLI.** `dotnet tool install -g ShellDocs.CLI --prerelease`, then `shelldocs init` for a new site or in an existing Blazor Web App. Keep the `ShellDocs.*` package versions and the CLI version the same. 2. **Pages are Markdown files** in `content/docs/`, with `title` and `description` frontmatter. Each folder's `meta.json` sets the sidebar order and must list every page. `shelldocs add page|guide|component ` scaffolds one. New pages and `meta.json` edits need a restart of the running site. 3. **Use ShellDocs' components in Markdown; don't write HTML look-alikes.** ``, `` / ``, `` / ``, `` / `` / ``, ``, ``, `` / `` and ``. When a site's own library has a component of the same name, the `Docs` prefix (``) still reaches the built-in. 4. **Live examples:** a ```` ```razor:preview ```` fence, ``, or `` for a `.razor` file under `DemoSourceRoot`. A `razor:preview` body is Razor, not Markdown: use `` and ``, not backticks and `**`. Components used in Markdown must be registered with `RegisterComponent()` or `RegisterComponentsFromAssembly()`. 5. **Attribute values on tags in Markdown are plain text.** They aren't Markdown and HTML entities aren't decoded. There is no escape for the quote character, so switch between single and double quotes. 6. **Configuration lives in `AddShellDocs(o => ...)` in `Program.cs`:** site details, navigation, packages, versions, redirects, previews and `EnableLlmsTxt`. 7. **Static sites come from `shelldocs build`.** Don't edit the `publish/` output by hand, and don't add `--spa-fallback` unless the host needs it: it replaces the not-found page with the home page. --- # Introduction > What ShellDocs is, why it exists, and where it fits in the .NET ecosystem. Source: https://shelldocs.shellui.dev/docs/introduction **ShellDocs is the docs framework for .NET.** Beautiful, animated, `Cmd+K`-searchable documentation sites, powered by Blazor and Tailwind. Compose with ShellUI (or any Blazor component library) : like fumadocs composes with shadcn/ui in the React world. > **Alpha status** > > ShellDocs is in alpha; these docs cover `0.1.17-alpha`. The primitive surface is stable enough to build real docs sites : this site is built with ShellDocs itself. Expect small API changes before `1.0`. ## Why ShellDocs exists Every .NET UI library ends up hand-rolling its own docs site. MudBlazor, Radzen, AvaloniaUI : each spent months rebuilding a sidebar, a search box, a code block, a theme toggle. None of it was reusable. ShellDocs is the "just use this" answer. Point it at a folder of markdown, get a working docs site : with the design polish React consumers have taken for granted since fumadocs shipped in 2023. ## What you get - **Markdown-first authoring**: YAML frontmatter, fenced code blocks with Shiki highlighting, live-rendered razor:preview examples, and inline Razor component tags mid-prose. - **Auto-wired navigation**: File-based routing. Drop a .md into content/, it becomes a page. Sidebar, breadcrumb, prev/next, TOC : all derived from the tree. - **Cmd+K search**: Built into the app. Matches titles, descriptions, headings and body text, with snippets. No external search service to set up. - **Composable**: Bring your own component library. Register in one line via RegisterComponentsFromAssembly. Reference in markdown. - **Live previews**: Components render as real Razor components, not screenshots, with Preview and Code tabs on every example. - **Versioned docs**: One content folder per release, a version selector, and navigation that stays inside the version being read. - **Static site output**: shelldocs build prerenders every page for any static host, with redirects, a sitemap and social meta tags. ## How it looks in practice Author a page as plain markdown with frontmatter: ````markdown --- title: Button description: A pressable UI element. --- # Button The ` ``` ```` Drop inline components anywhere in prose: ````markdown This behavior changed in `0.2.0`. Older versions used `OnClick` instead of `Pressed`. ```` The whole thing renders : Callout with icon and variant styling, live Button component, syntax-highlighted code samples, working TOC : with zero JavaScript beyond what Blazor and Shiki need. ## The three packages you'll touch most | Name | Type | Default | Description | |------|------|---------|-------------| | `ShellDocs.CLI` | `global tool` | | shelldocs init, add, dev, build. Installed once, works from any directory. | | `ShellDocs.Components` | `Razor Class Library` | | The chrome (layout, sidebar, search, header) and content primitives (Callout, Card, Steps, CodeGroup, TypeTable, more). | | `ShellDocs.Tokens` | `Razor Class Library` | | Shared CSS variables : palette + spacing scale. shadcn-compatible names for interop with ShellUI and other design systems. | ## Where to go next - [Installation](https://shelldocs.shellui.dev/docs/getting-started/installation.md): Install the CLI, scaffold a project, run it locally. - [Quick start](https://shelldocs.shellui.dev/docs/getting-started/quick-start.md): From zero to a live docs site in five minutes. - [Component reference](https://shelldocs.shellui.dev/docs/components/callout.md): Callout, Card, Steps, Tabs, CodeGroup, FileTree, TypeTable and the preview primitives. - [Authoring guide](https://shelldocs.shellui.dev/docs/authoring.md): Frontmatter, razor:preview, inline component tags, navigation. - [Documenting a component library](https://shelldocs.shellui.dev/docs/guides/component-libraries.md): Register your components, preview them, generate props tables. - [Deployment](https://shelldocs.shellui.dev/docs/guides/deployment.md): Static export or a .NET host. --- # Installation > Install the ShellDocs CLI, add the framework to a Blazor project. Source: https://shelldocs.shellui.dev/docs/getting-started/installation Two things to install: the **CLI** (a .NET global tool) and the **framework packages** (referenced from your docs project). Most consumers use the CLI to scaffold a fresh project, which wires the packages automatically. ## Prerequisites | Name | Type | Default | Description | |------|------|---------|-------------| | `.NET SDK` (required) | `10.0+` | | .NET 10 or newer. Verify with dotnet --version. | | `git` | `any` | | For version control. Not strictly required to run ShellDocs : required for publishing to GitHub Pages / Cloudflare / Netlify from a repo. | ## Install the CLI `ShellDocs.CLI` is a .NET global tool. Install once, use from any directory. ```bash dotnet tool install -g ShellDocs.CLI --prerelease ``` > **Why --prerelease** > > The `--prerelease` flag is required while ShellDocs is on `0.x.y-alpha` versions. `dotnet tool install` skips prerelease packages by default. Drop the flag once `1.0` ships. Verify: ```bash shelldocs --help ``` You should see `init`, `add`, `dev` and `build` in the command list. (`preview` is listed too, but is not implemented yet.) ## Update the CLI ```bash dotnet tool update -g ShellDocs.CLI --prerelease ``` Pulls the latest published version from nuget.org. ## The framework packages If you're scaffolding via `shelldocs init`, the CLI adds these to your `.csproj` automatically. If you're wiring an existing Blazor project by hand, add: **dotnet CLI** ```bash dotnet add package ShellDocs.Components --prerelease dotnet add package ShellDocs.Tokens --prerelease ``` **PackageReference** ```xml ``` `ShellDocs.Core` and `ShellDocs.Markdown` come in transitively : no need to reference them directly. ## The package family | Name | Type | Default | Description | |------|------|---------|-------------| | `ShellDocs.CLI` | `global tool` | | shelldocs init, add, dev, build. Installed once, works from any directory. | | `ShellDocs.Components` | `RCL` | | The chrome (layout, sidebar, search, header) and content primitives (Callout, Card, Steps, CodeGroup, TypeTable, more). | | `ShellDocs.Tokens` | `RCL` | | Shared CSS variables. shadcn-compatible names for interop with ShellUI and other design systems. | | `ShellDocs.Core` | `library (transitive)` | | Navigation graph, search index, markdown plain-text extraction. | | `ShellDocs.Markdown` | `library (transitive)` | | Markdig pipeline, frontmatter parser, razor:preview and inline-component slot extractor. | ## Next - [Quick start](https://shelldocs.shellui.dev/docs/getting-started/quick-start.md): Scaffold a fresh site and see it running in ~5 minutes. - [Project structure](https://shelldocs.shellui.dev/docs/getting-started/project-structure.md): What shelldocs init created, and what each folder does. --- # Quick start > From zero to a working docs site in five minutes. Source: https://shelldocs.shellui.dev/docs/getting-started/quick-start Assumes you've already run `dotnet tool install -g ShellDocs.CLI --prerelease`. If not, hop over to [Installation](https://shelldocs.shellui.dev/docs/getting-started/installation.md) first. **1. Scaffold** Pick a directory (empty or your existing repo), then: ```bash shelldocs init MyDocs ``` Creates a new Blazor Web App under `docs/MyDocs.Docs/`, wires the ShellDocs packages, patches `Program.cs`, drops a starter `content/docs/introduction.md`. One command → running docs site. Prefer to augment an existing Blazor project instead of scaffolding fresh? Use `--attach`: ```bash shelldocs init --attach --dir path/to/your/blazor/project ``` Attach mode never patches your `Program.cs` (respects your custom middleware / auth) : it emits a `SHELLDOCS_SETUP.md` with copy-paste snippets instead. **2. Run the dev server** ```bash cd docs/MyDocs.Docs shelldocs dev ``` Serves at http://localhost:5000 with hot reload on `.md`, `.razor`, and `.cs` changes. Or, equivalently: ```bash dotnet watch run ``` `shelldocs dev` is a thin wrapper : same underlying behavior, plus markdown-file watching baked in. **3. Open the site** Visit http://localhost:5000. You should see: ```text Welcome page # Home.razor : customize this into your marketing page /docs/introduction # the starter page ShellDocs scaffolded for you ``` The sidebar auto-populates from your `content/docs/` folder. The Introduction page renders your starter markdown with a live `` component and syntax-highlighted code. **4. Add a new page** ```bash shelldocs add component Button ``` Creates `content/docs/components/button.md` with frontmatter, a `razor:preview` skeleton, and an empty `` for props documentation. Reload the browser : the page appears in the sidebar automatically. No `meta.json` edit required. Other templates: | Name | Type | Default | Description | |------|------|---------|-------------| | `component {Name}` | `template` | | content/docs/components/{slug}.md : razor:preview + Props table + Notes | | `guide {slug}` | `template` | | content/docs/guides/{slug}.md : intro + Steps skeleton + Next section | | `page {slug}` | `template` | | content/docs/{slug}.md : blank frontmatter + H1 | **5. Publish to static hosting** ```bash shelldocs build --output publish ``` Prerenders every page into `publish/` as static files. Upload the folder to any static host. See [Deployment](https://shelldocs.shellui.dev/docs/guides/deployment.md) for a GitHub Pages workflow and for what needs a server instead. ## What just happened You now have a Blazor Web App with: - **File-based routing** : drop a `.md` in `content/docs/`, it becomes a page - **Auto sidebar** : the tree structure of `content/docs/` becomes the nav - **Live components** : `razor:preview` fences render real Blazor components inline - **Search** : `Cmd+K` opens the search dialog, on a .NET host and on a static export alike - **Dark mode** : theme toggle in the sidebar footer ## Next - [Project structure](https://shelldocs.shellui.dev/docs/getting-started/project-structure.md): Understand what's in the folders shelldocs init created. - [Authoring guide](https://shelldocs.shellui.dev/docs/authoring.md): Frontmatter, code fences, razor:preview, inline components. --- # Project structure > What ShellDocs scaffolds, and what each folder does. Source: https://shelldocs.shellui.dev/docs/getting-started/project-structure After `shelldocs init MyDocs` from a directory called `foo`, you'll get: ```text foo/ docs/ # scaffold root : everything ShellDocs touches lives here MyDocs.Docs/ # the Blazor Web App Components/ App.razor # HTML shell : tokens.css, theme bootstrap, Shiki loader Routes.razor # Router : DefaultLayout=DocsLayout Pages/ Home.razor # landing / welcome page : edit into your marketing surface DocsPage.razor # catch-all /docs/{*Path} : renders markdown Layout/ MainLayout.razor # pass-through fallback (real layout is DocsLayout from the RCL) content/ # all authored markdown lives here docs/ introduction.md # the starter page meta.json # sidebar ordering (optional : files auto-appear without it) Program.cs # AddShellDocs configuration lives here MyDocs.Docs.csproj # references ShellDocs.Components + ShellDocs.Tokens ``` ## The four surfaces you'll touch - **content/docs/**: Where your authoring lives. Every .md file becomes a page. Every subfolder becomes a sidebar section. - **Program.cs**: Site config: name, tagline, GitHub link, layout variant, nav links, package selector, brand logo. - **Components/Pages/Home.razor**: Your landing page. Ships as a welcome hero : edit into your product's marketing surface. - **Components/App.razor**: HTML shell. Rarely edited : but useful when you want custom favicons, analytics scripts, or extra token overrides. ## What ShellDocs adds to your `.csproj` | Name | Type | Default | Description | |------|------|---------|-------------| | `ShellDocs.Components` (required) | `PackageReference` | | RCL with all the chrome + primitives. | | `ShellDocs.Tokens` (required) | `PackageReference` | | CSS variables : palette + spacing scale. | | `Content Update` | `ItemGroup` | `content/**/*.md;content/**/meta.json` | Ensures markdown gets copied on dotnet publish : otherwise deployed sites 404 every page. | ## Content routing The catch-all page in `Components/Pages/DocsPage.razor` maps every URL under `/docs/*` to a markdown file: | Name | Type | Default | Description | |------|------|---------|-------------| | `/docs/introduction` | `URL` | `content/docs/introduction.md` | Top-level page | | `/docs/components/callout` | `URL` | `content/docs/components/callout.md` | Nested via subfolder | | `/docs/guides/getting-started` | `URL` | `content/docs/guides/getting-started.md` | Two-level nesting works recursively | ## meta.json : optional ordering control Each folder in `content/docs/` can have a `meta.json` that controls sidebar ordering: ```json { "title": "Documentation", "pages": [ "introduction", "---", { "title": "Getting Started", "pages": ["installation", "quick-start"] }, "components" ] } ``` > **meta.json controls order, not presence** > > Files you don't list in `pages` still appear in the sidebar : they get appended alphabetically after your explicit ordering. So you can drop a new `.md` in and it shows up without touching `meta.json`. Only edit `meta.json` when you want a specific position, divider, or grouped subsection. ## Next - [Configuration](https://shelldocs.shellui.dev/docs/getting-started/configuration.md): The full ShellDocsOptions surface : logo, nav, packages, layout variant, registered components. - [Authoring guide](https://shelldocs.shellui.dev/docs/authoring.md): Frontmatter, code fences, razor:preview, inline components. --- # Configuration > Every option on ShellDocsOptions. Site details, layout, navigation, packages, versions, redirects, branding and component registration. Source: https://shelldocs.shellui.dev/docs/getting-started/configuration A ShellDocs site is configured in one place: the `AddShellDocs` call in `Program.cs`. ## The minimum ```csharp builder.Services.AddShellDocs(o => { o.ContentRoot = Path.Combine(builder.Environment.ContentRootPath, "content"); o.SiteName = "MyDocs"; o.GitHubRepo = "my-org/my-docs"; }); ``` That is a working site. The built-in content primitives are registered for you. ## Site details | Name | Type | Default | Description | |------|------|---------|-------------| | `SiteName` (required) | `string` | `empty` | Brand name shown in the header and sidebar. | | `SiteTagline` | `string?` | `—` | One-line description shown in the footer. | | `GitHubRepo` | `string?` | `—` | Owner and repository, for example my-org/my-docs. Used for the GitHub link and as the default issue tracker. | | `SiteUrl` | `string?` | `—` | Absolute address of the deployed site. Used in the links the preview menu builds and in the llms.txt files. | | `IssueTrackerUrl` | `string?` | `GitHubRepo issues` | Where the preview menu's Report a bug and Suggest something open a new issue. With neither this nor GitHubRepo set, those items are hidden. | | `ContentRoot` | `string` | `content` | Path to the content folder. | The sitemap and social meta tags of a static export come from the `--site-url` flag of [shelldocs build](https://shelldocs.shellui.dev/docs/cli/build.md). ## Layout and page header | Name | Type | Default | Description | |------|------|---------|-------------| | `LayoutVariant` | `DocsLayoutVariant` | `TopNav` | TopNav is a header across the top with the sidebar below. Sidebar has no top header; brand and search live in a floating sidebar. This site uses Sidebar. | | `RenderPageTitle` | `bool` | `false` | Renders the frontmatter title as the page heading, and the description as a lead paragraph, on pages whose body does not start with its own heading. | ```csharp o.LayoutVariant = DocsLayoutVariant.Sidebar; o.RenderPageTitle = true; ``` ## Header navigation | Call | Adds | |---|---| | `AddNavLink(label, href)` | A link in the primary nav | | `AddNavMenu(label, items…)` | A dropdown of links, each with an optional description and icon | ```csharp o.AddNavLink("Docs", "/docs/introduction"); o.AddNavLink("Releases", "https://github.com/my-org/my-lib/releases"); o.AddNavMenu("Learn", new NavMenuItem("Getting started", "/docs/getting-started/installation", "Install and scaffold your first site."), new NavMenuItem("Authoring", "/docs/authoring", "Write pages in markdown.")); ``` On small screens the primary nav becomes a menu behind the hamburger button. ## Package selector For a product made of several packages, the sidebar can show a selector that jumps between each package's docs. ```csharp o.AddPackage("mylib.core", "MyLib.Core", "The core library.", "/docs/core"); o.AddPackage("mylib.cli", "MyLib.CLI", "The command line.", "/docs/cli"); ``` | Name | Type | Default | Description | |------|------|---------|-------------| | `id` (required) | `string` | | Unique identifier. | | `title` (required) | `string` | | Name shown in the selector. | | `description` (required) | `string` | | Second line in the menu. | | `rootUrl` (required) | `string` | | Where selecting the package goes. May contain the version token. | | `iconPath` | `string?` | | The d attribute of an SVG path on a 24 by 24 view box. Not a URL. | The selector is hidden with fewer than two packages. The selected package is the one whose root URL is the longest prefix of the current path. ## Versions ```csharp o.AddVersion("v2.0", "v2.0.0", "/docs/v2.0", "Current stable", latest: true); o.AddVersion("v1.9.1", "v1.9.1", "/docs/v1.9.1", "Previous release"); ``` Registering versions adds a version selector and scopes the sidebar, search, breadcrumb and prev/next links to the version being read. The full walk-through is in [Versioning](https://shelldocs.shellui.dev/docs/guides/versioning.md). ## Redirects | Name | Type | Default | Description | |------|------|---------|-------------| | `EnableRedirects` | `bool` | `true` | Redirects folder URLs, version roots and unversioned URLs to the right page. | | `AddRedirect(from, to, permanent)` | `method` | | A rule for moved content. Matches by path segment and covers everything under the source path. Permanent by default. | ```csharp o.AddRedirect("/docs/setup", "/docs/getting-started/installation"); ``` See [Redirects](https://shelldocs.shellui.dev/docs/guides/redirects.md). ## Brand logo | Name | Type | Default | Description | |------|------|---------|-------------| | `LogoSvg` | `string?` | `—` | Inline SVG markup. Use currentColor and one logo serves both themes. Rendered as raw markup, so only pass content you wrote. | | `LogoLight / LogoDark` | `string?` | `—` | Image URLs for each theme, for logos with fixed colours. | | `LogoAlt` | `string?` | `SiteName` | Accessible label for the logo. | | `LogoHeight` | `double` | `1.375` | Height in rem. | | `SetLogo(url, alt)` | `method` | | One image for both themes. | | `SetLogo(light, dark, alt)` | `method` | | An image per theme. | ```csharp o.LogoSvg = File.ReadAllText(Path.Combine(builder.Environment.WebRootPath, "brand/logo.svg")); ``` ## Registering components Built-in primitives are always available. Register your own so their tags work in markdown and previews. | Call | Registers | |---|---| | `RegisterComponent()` | One component | | `RegisterComponent(tagName)` | One component under a different tag name | | `RegisterComponentsFromAssembly()` | Every public component in the marker's assembly | | `RegisterComponentsFromAssembly(namespacePrefix)` | Only components in namespaces starting with the prefix | | `RegisterComponentsFromAssembly(filter)` | Only components the filter accepts | ```csharp o.RegisterComponentsFromAssembly(); o.RegisterComponentsFromAssembly("MyDocs.Components.Demos"); ``` A component of yours with the same name as a built-in wins the tag; the built-in stays available under a `Docs` prefix. Generic components register under their bare name. Mark a component with `[ShellDocsIgnore]` to leave it out. All of this is covered in [Documenting a component library](https://shelldocs.shellui.dev/docs/guides/component-libraries.md). | Name | Type | Default | Description | |------|------|---------|-------------| | `DemoSourceRoot` | `string?` | `—` | Folder searched for the .razor file that DemoPreview shows as source. | ## Previews | Name | Type | Default | Description | |------|------|---------|-------------| | `PreviewCaption` | `string?` | `—` | A note shown in the toolbar of every preview. A preview's own Caption replaces it. | | `AddSyncSwitch(syncKey, options)` | `method` | | Adds a row of tabs to every page whose blocks use that SyncKey. Needs at least two options; the first is the default. | ```csharp o.PreviewCaption = "Rendered with Blazor"; o.AddSyncSwitch("platform", "MAUI", "Avalonia"); ``` Both are covered in [Code tabs on previews](https://shelldocs.shellui.dev/docs/components/code-tabs.md). ## AI agents | Name | Type | Default | Description | |------|------|---------|-------------| | `EnableLlmsTxt(configure)` | `method` | | Serves /llms.txt, /llms-full.txt, every page as Markdown and agent instruction files, and adds the page actions menu by the title. | | `LlmsTxt` | `LlmsTxtOptions?` | `null` | The options EnableLlmsTxt set. Null while it is off. | ```csharp o.EnableLlmsTxt(l => { l.RulesFile = "Llms/agent-rules.md"; l.InstallCommandPattern = @"dotnet add package MyLib\.\w+"; }); ``` The options and the files are covered in [AI agents (llms.txt)](https://shelldocs.shellui.dev/docs/guides/llms-txt.md). ## Options that do nothing yet `Theme` and the `--theme` flag of `shelldocs init` are accepted but have no effect in this release. Every site uses the same tokens; restyle through [Theming](https://shelldocs.shellui.dev/docs/theming.md). ## This site's configuration ```csharp builder.Services.AddShellDocs(o => { o.ContentRoot = Path.Combine(builder.Environment.ContentRootPath, "content"); o.SiteName = "ShellDocs"; o.SiteTagline = "The docs framework for .NET."; o.GitHubRepo = "shellui-dev/shelldocs"; o.SiteUrl = "https://shelldocs.shellui.dev"; o.LayoutVariant = DocsLayoutVariant.Sidebar; o.LogoSvg = File.ReadAllText(Path.Combine(builder.Environment.WebRootPath, "brand", "mark.svg")); o.RegisterComponentsFromAssembly("ShellDocs.Site.Components.Demos"); o.DemoSourceRoot = Path.Combine(builder.Environment.ContentRootPath, "Components", "Demos"); o.EnableLlmsTxt(l => { l.Summary = "ShellDocs is a docs framework for .NET: Markdown pages, live Blazor component previews, search and a static export, in a Blazor Web App."; l.RulesFile = "Llms/agent-rules.md"; l.InstallCommandPattern = @"(?m)(?<=^[ \t]*)dotnet (?:tool install -g ShellDocs\.CLI|add package ShellDocs\.\w+)[^\r\n]*"; }); o.AddNavLink("Documentation", "/docs/introduction"); o.AddNavLink("Releases", "https://github.com/shellui-dev/shelldocs/releases"); o.AddPackage("shelldocs", "ShellDocs", "The framework.", "/docs/introduction"); o.AddPackage("shelldocs.components", "ShellDocs.Components", "Chrome and primitives.", "/docs/packages/components"); o.AddPackage("shelldocs.markdown", "ShellDocs.Markdown", "The markdown pipeline.", "/docs/packages/markdown"); o.AddPackage("shelldocs.cli", "ShellDocs.CLI", "The command line.", "/docs/packages/cli"); }); ``` ## Next - [Authoring](https://shelldocs.shellui.dev/docs/authoring.md): Write pages: frontmatter, code fences, live previews. - [Component reference](https://shelldocs.shellui.dev/docs/components/callout.md): Every shipped primitive. - [Documenting a component library](https://shelldocs.shellui.dev/docs/guides/component-libraries.md): Register and preview your own components. - [Deployment](https://shelldocs.shellui.dev/docs/guides/deployment.md): Static export or a .NET host. - [AI agents (llms.txt)](https://shelldocs.shellui.dev/docs/guides/llms-txt.md): Docs that coding agents can read. --- # Authoring > How to write pages. Markdown, frontmatter, fenced code, live component demos, inline component tags, navigation. Source: https://shelldocs.shellui.dev/docs/authoring Everything ShellDocs asks of you as an author lives here : the frontmatter fields, the fenced-code conventions, how to drop a live component demo mid-page, how the sidebar decides where things go. ## The one-minute overview A ShellDocs page is a `.md` file under `content/`. Drop one in, it appears in the sidebar automatically : no registration, no import, no meta.json edit required. ````markdown --- title: My Page description: What this page is about. order: 3 --- # My Page Regular markdown. Fenced code blocks: ```csharp var x = 1; ``` Inline components mid-prose: Something worth calling out. Live component demos: ```razor:preview ``` ```` That's the whole surface area for authoring. ## Anatomy of a page | Name | Type | Default | Description | |------|------|---------|-------------| | `Frontmatter` | `YAML` | | Top-of-file title / description / order (+ arbitrary keys). Controls sidebar entry, meta tags, and ordering. | | `Body` | `markdown` | | CommonMark + GFM pipe tables + task lists + footnotes. Softline breaks become hard breaks (edit-friendly for prose). | | `Fenced code` | `fence` | | Standard language fences render as highlighted code with a copy button and a language badge. | | `razor:preview` | `live example` | | A fence that renders live components with Preview and Code tabs. Its body is Razor. | | `Inline components` | `tag` | | Component tags in the page become real Blazor components. Their bodies are markdown. | ## Where pages live Every `.md` under `content/docs/**` becomes a page. The URL is derived from the folder path: ```text content/ docs/ introduction.md # /docs/introduction getting-started/ installation.md # /docs/getting-started/installation quick-start.md # /docs/getting-started/quick-start ``` > **index.md is special** > > An `index.md` in a folder gets the folder's URL. `content/docs/guides/index.md` → `/docs/guides` (not `/docs/guides/index`). Useful for landing pages at section roots. ## Seeing your edits Run the site with `shelldocs dev` (or `dotnet watch run`). - **Editing an existing page** shows on the next browser refresh. Pages are rendered from disk on each request. - **Adding, renaming or deleting a page, or editing a `meta.json`,** changes the navigation graph, which is built when the app starts. Restart the dev server to pick those up. - **Editing `.razor` or `.cs` files** goes through hot reload as in any Blazor app. ## Where to go next - [Frontmatter](https://shelldocs.shellui.dev/docs/authoring/frontmatter.md): Every field ShellDocs reads from the YAML block. - [Fenced code](https://shelldocs.shellui.dev/docs/authoring/fenced-code.md): Language detection, copy buttons, Shiki highlighting. - [razor:preview](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md): The live-component-demo fence. - [Inline component tags](https://shelldocs.shellui.dev/docs/authoring/component-tags.md): Drop a Callout mid-prose : how it works. - [Navigation](https://shelldocs.shellui.dev/docs/authoring/navigation.md): meta.json, order, dividers, hidden pages. - [Components reference](https://shelldocs.shellui.dev/docs/components/callout.md): Every primitive you can drop into markdown. - [Versioning](https://shelldocs.shellui.dev/docs/guides/versioning.md): One docs tree per released version. - [Redirects](https://shelldocs.shellui.dev/docs/guides/redirects.md): Folder URLs and moved pages. --- # Frontmatter > YAML metadata at the top of every page. What ShellDocs reads, and what's free for your own use. Source: https://shelldocs.shellui.dev/docs/authoring/frontmatter Every `.md` page can carry a YAML block at the top, delimited by `---` on the first line and a matching `---` after the keys. `ShellDocs.Core`'s `FrontmatterParser` reads it via YamlDotNet, and the framework consumes a small handful of well-known keys. ## Shape ```markdown --- title: My Page description: Short summary shown as page subtitle and meta description. order: 3 category: Guides --- # My Page Body starts here. ``` Both delimiters are literal `---` on their own line. The block is optional : a page with no frontmatter still renders (title falls back to the slug). ## Fields ShellDocs reads | Name | Type | Default | Description | |------|------|---------|-------------| | `title` | `string` | | Sidebar label, breadcrumb tail, page title tag. Falls back to a title-cased slug (my-page.md → My Page). | | `description` | `string` | | Sidebar tooltip, page subtitle under H1, meta description. Indexed by search : matches on description weight higher than body matches. | | `order` | `int` | `0` | Sort order within a folder when meta.json doesn't list the page explicitly. Lower first; ties fall back to alphabetical. | | `category` | `string?` | | Optional grouping label. Currently surfaced as NavigationNode.Category : reserved for future sidebar grouping. | ## Using the title as the page heading By default the page heading is whatever `# Heading` the body starts with. Turn on `RenderPageTitle` and a page whose body does not start with a heading gets one from frontmatter: `title` becomes the heading and `description` a lead paragraph under it. ```csharp builder.Services.AddShellDocs(o => { o.RenderPageTitle = true; }); ``` A body that starts with its own `# Heading` is left as written, so you can adopt this page by page. ## Free-form keys Any additional keys are parsed into the frontmatter dictionary and available via `RenderedDocument.Frontmatter.GetValue("key")`. Useful for: - Author / date / revision (surface in your own layout) - Feature flags (`draft: true`, `experimental: true`) - Cross-tool metadata (custom fields your CI pipeline reads) ShellDocs itself ignores keys it doesn't recognize : nothing breaks if you add your own. ## Type coercion `GetValue` returns the value cast to `T`, using `Convert.ChangeType` when the raw parse doesn't match. So `order: 3` in YAML lands as a `long` from YamlDotNet, then coerces to `int` at read time. Pattern: ```csharp int order = doc.Frontmatter.GetValue("order"); string? draft = doc.Frontmatter.GetValue("draft"); ``` If the key is missing or coercion fails, `GetValue` returns `default(T)`. ## Multiline strings Standard YAML block-scalars work : `|` preserves newlines, `>` folds them into spaces. ```yaml --- title: Migration description: > Multi-line description that folds line breaks into single spaces so you don't have to fight rewrapping. --- ``` ## Escaping quotes and colons Values with colons or leading punctuation need quoting : YAML parses them as flow-scalars otherwise. ```yaml --- title: "Callouts : tips, warnings, and danger boxes" description: "The `Callout` primitive." --- ``` Prefer double-quotes so backticks stay literal. ## When frontmatter is missing or broken - **No `---` block at all** : the page still renders. Title falls back to the slug. - **Opening `---` with no closing `---`** : parser treats it as no frontmatter and passes the whole file through as body. - **Malformed YAML** : parser catches the `YamlException` and returns an empty frontmatter dict, still handing the body through untouched. The page renders with slug-derived title. > **Silent-fail semantics** > > Broken YAML doesn't throw : the page just renders with defaults. If a page's sidebar entry suddenly reverts to a slug-cased title, check the frontmatter for a stray unescaped colon. ## See also - [Navigation](https://shelldocs.shellui.dev/docs/authoring/navigation.md): How order interacts with meta.json. - [ShellDocs.Core](https://shelldocs.shellui.dev/docs/packages/core.md): FrontmatterParser API details. --- # Fenced code > Regular code fences: language detection, copy button, Shiki syntax highlighting. Source: https://shelldocs.shellui.dev/docs/authoring/fenced-code Fenced code blocks (` ``` `) are the workhorse of docs authoring. ShellDocs wraps every fence in chrome : a language badge, a copy button, and Shiki-driven syntax highlighting. You don't opt in : any fence with a language tag gets the treatment. ## Basic use Standard CommonMark : three backticks, a language name, the code, three backticks. ````markdown ```csharp var pipeline = new MarkdownPipelineBuilder() .UsePipeTables() .Build(); ``` ```` Renders as: ```csharp var pipeline = new MarkdownPipelineBuilder() .UsePipeTables() .Build(); ``` ## Language detection The language name after the opening ` ``` ` becomes a `language-` class on the `` element. `CodeBlockEnhancer` extracts it, renders a badge in the header, and Shiki uses it for highlighting. Common values: `csharp`, `razor`, `bash`, `json`, `yaml`, `xml`, `html`, `css`, `typescript`, `python`. Any Shiki-supported language works. ## The copy button Every fence gets a copy button in the top-right. Click copies the raw text : language badge, whitespace, and newlines are preserved exactly as authored. On success it flashes a checkmark for ~1 s. No configuration : it's on by default. ## No-language fences Fences without a language tag still render : just no badge and no highlighting. Useful for shell prompts or ad-hoc text. ````markdown ``` $ shelldocs init MyDocs ✓ Wrote 12 change(s): ``` ```` Renders as: ``` $ shelldocs init MyDocs ✓ Wrote 12 change(s): ``` ## Indented code Four-space-indented code blocks still work (CommonMark supports them) but you lose the language badge and the copy button. **Prefer fenced blocks** : they're what all the chrome hooks into. ## Inline code Backtick-wrapped inline code (`` `like this` ``) is styled but has no copy button or language handling. Use it for identifiers, short snippets, and API references : ``, `AddShellDocs()`, `Program.cs`. ## Longer fences and tildes A fence is three or more backticks, closed by a run at least as long. Inside it everything is verbatim : no markdown parsing, no component tags, no previews. To show a fence as an example, as this page does, wrap it in a longer one: `````markdown ````markdown ```csharp var x = 1; ``` ```` ````` Tilde fences work the same way and save counting backticks: ````markdown ~~~markdown ```razor:preview Shown as source, not rendered. ``` ~~~ ```` A component tag or a `razor:preview` fence inside a longer fence stays literal, and whatever follows the block on the page renders normally. ## The `razor:preview` fence The special `razor:preview` language turns the fence into a live component demo instead of a code block. Covered in [razor:preview](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md). ## CodeGroup for tabbed variants For "same thing in three package managers" or "before/after" tabs, wrap fences in a `CodeGroup`: **dotnet CLI** ```bash dotnet add package ShellDocs.Components --prerelease ``` **PackageReference** ```xml ``` For panels that are not code, use [Tabs](https://shelldocs.shellui.dev/docs/components/tabs.md). Full docs: [CodeGroup](https://shelldocs.shellui.dev/docs/components/code-group.md). ## What ShellDocs doesn't do - **Line numbers** : no first-class support. Add them by wrapping the fence in a custom component if you need them. - **Line highlighting** : no `{1,3-5}`-style annotations yet. - **Diff rendering** : `language-diff` highlights as diff syntax (via Shiki) but there's no side-by-side viewer. ## See also - [razor:preview](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md): Live-component-demo fences. - [CodeGroup](https://shelldocs.shellui.dev/docs/components/code-group.md): Tabbed code blocks. --- # `razor:preview` > The fence that renders as a live example with Preview and Code tabs. Source: https://shelldocs.shellui.dev/docs/authoring/razor-preview A `razor:preview` fence renders its contents as live components and shows the same markup in a Code tab. You write the example once, as Razor, and the reader sees it running and can copy it. ## Basic use ````markdown ```razor:preview This renders as a real Callout component. ``` ```` ```razor This renders as a real Callout component. ``` The tags can be components registered with ShellDocs : the built-in primitives, or [your own](https://shelldocs.shellui.dev/docs/guides/component-libraries.md). A fence of plain HTML renders live too: ```razor

A fence with only HTML and no component.

``` A fence with no tags at all stays a code block, and a warning is logged. Before `0.1.17-alpha` an HTML-only fence also rendered as a code block. ## The body is Razor, not markdown Everything inside the fence is parsed as Razor markup, so the Code tab shows exactly what belongs in a `.razor` file. Elements wrap nested components as written and no text is wrapped in a paragraph. Markdown syntax inside a fence renders literally. Use HTML for inline formatting: ```razor Run shelldocs dev, then open /docs/introduction. ``` > **Markdown bodies live in the page, not the fence** > > A component tag written straight into the page takes a markdown body. See [Inline component tags](https://shelldocs.shellui.dev/docs/authoring/component-tags.md). ## Several components and wrapper elements A fence can hold any number of top-level tags. They render in order inside one frame. ```razor Two components in one fence. Rendered in order. ``` Plain HTML wrappers work too, which is how you lay an example out: ```razor
``` ## Named slots A direct child tag whose name matches one of the component's `RenderFragment` parameters goes into that slot. Everything else becomes `ChildContent`. ```razor … Body text.
Small print.
``` Here `Icon` and `Footer` fill `Alert.Icon` and `Alert.Footer`, and the loose text fills `Alert.ChildContent`. This is what lets a fence preview composed components such as dialogs, cards with headers, and tab lists. ## Attribute values Attributes are written as in Razor and converted to each parameter's type. | Written as | Parameter type | Result | |---|---|---| | `Columns="3"` or `Columns="@3"` | `int` | `3` | | `External="true"` or `External="@true"` | `bool` | `true` | | `Variant="Destructive"` | enum | Looked up by name, ignoring case | | `Variant="ButtonVariant.Destructive"` | enum | The qualified form works too, with or without `@` | | `Sides="Left \| Right"` | flags enum | Combined | | `Href="/docs"` | `string` | Left as written | Parameter names match without regard to case, as in Blazor. Some attributes can't be applied from markdown: event callbacks (`OnClick="Save"`), `@bind-*`, `@ref`, `@onclick`, and values that can't be converted. They are skipped with a logged warning, the component still renders, and the Code tab still shows them. That keeps a realistic example copyable without breaking the page. Lines that are not markup, such as `@code { }` blocks, `@* comments *@` and `@using` lines, are left out of the render and kept in the Code tab. ## Generic components A generic component registers under its bare name. Close it with a type-parameter attribute, as in Razor: ```razor ``` Type arguments use C# spellings: `int?`, `List`, or a simple or full type name. Without one, `object` is used when the constraints allow it and a warning is logged. If the type can't be closed the frame shows an error instead of breaking the page. ## Layout Add `stretch` or `scroll` after the language to change how the example sits in the frame. ````markdown ```razor:preview stretch … ``` ```` | Name | Type | Default | Description | |------|------|---------|-------------| | `razor:preview` | `center` | | Centred, sized to its content. The default. | | `razor:preview stretch` | `stretch` | | Block-level components fill the frame. | | `razor:preview scroll` | `scroll` | | A wide example scrolls inside the frame on small screens. | More on each in [PreviewFrame](https://shelldocs.shellui.dev/docs/components/preview-frame.md). ## Unknown components If no tag in the fence is a registered component, the frame shows an error panel that names the tag and how to register it. A warning is written to the log as well. ## When a fence isn't enough | Name | Type | Default | Description | |------|------|---------|-------------| | `Needs a code block, state or a loop` | `DemoPreview` | | Write the example as a .razor file in the docs project. Its file is the source shown. | | `Has to sit inside another tag's body` | `ComponentPreview` | | A preview written as a tag. | ## See also - [PreviewFrame](https://shelldocs.shellui.dev/docs/components/preview-frame.md): The toolbar, layouts and anchors. - [DemoPreview](https://shelldocs.shellui.dev/docs/components/demo-preview.md): Examples with their own code block. - [Inline component tags](https://shelldocs.shellui.dev/docs/authoring/component-tags.md): Components in page prose, with markdown bodies. - [Documenting a component library](https://shelldocs.shellui.dev/docs/guides/component-libraries.md): Registering your own components. --- # Inline component tags > Write a component tag straight into the page. It renders as the real component, and its body is markdown. Source: https://shelldocs.shellui.dev/docs/authoring/component-tags A component tag written in the page, outside any fence, renders as the real Blazor component. This is how you put a `Callout` between two paragraphs, a `CardGrid` at the end of a page, or `Steps` inside a section. ## The pattern ```markdown Some prose. Body text with `inline code` and **emphasis**. More prose. ``` Some prose. > **Something worth knowing** > > Body text with `inline code` and **emphasis**. More prose. ## The body is markdown An inline tag's body goes through the markdown pipeline: inline code, emphasis, links, lists, fenced code and further component tags all work. Leave a blank line between a tag and a fenced block. ````markdown ```bash dotnet tool install -g ShellDocs.CLI --prerelease ``` `shelldocs init MyDocs` creates a working site. ```` This is the opposite of a [`razor:preview`](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md) fence, whose body is Razor. The rule of thumb: a tag in the page is content, a fence is an example of code. ## Self-closing tags A tag with no body closes itself with `/>`. ```markdown ``` - [Quick start](https://shelldocs.shellui.dev/docs/getting-started/quick-start.md): Scaffold a fresh site. ## Naming rules - A tag name starts with an **uppercase letter**. That is how the parser tells a component from HTML: `` stays HTML, `` is looked up as a component. - Attribute values are **double-quoted**. - Attribute names match the component's parameters without regard to case. ## Attribute values Values are converted to each parameter's type, the same way a fence converts them: `Columns="3"` becomes an `int`, `External="true"` a `bool`, `Variant="warning"` an enum member. A quoted value can hold anything except its own quote character. Angle brackets, another tag, and text that looks like an attribute all stay inside the value: ```markdown ``` Three things to know: | Name | Type | Default | Description | |------|------|---------|-------------| | `Quotes` | `choose` | | There is no escape for a quote inside a value. Wrap the value in single quotes when it contains double quotes, and the other way round. | | `Markdown` | `literal` | | Attribute text is not markdown. Backticks and asterisks show up as typed. Put formatted text in the tag body. | | `HTML entities` | `literal` | | Entities are not decoded. Write the character itself. | A tag whose quote is never closed, or that runs across a blank line, is treated as plain text rather than a component. ## Code spans stay code A tag inside backticks is left alone. Writing `` `` `` in a sentence produces inline code, not a component. The same goes for anything inside a fenced block. ## Nested tags and named slots Tags nest to any depth. ```markdown A callout inside a card. ``` A direct child tag whose name matches one of the parent's `RenderFragment` parameters fills that slot instead of the default child content. See [named slots](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md). ## Unknown tags An uppercase tag that isn't a registered component is passed through as raw markup, and a warning is logged. Register the component: ```csharp builder.Services.AddShellDocs(o => { o.RegisterComponentsFromAssembly(); }); ``` ## When your library has the same name If your component library registers a `Card`, `` renders yours. ShellDocs' own primitive stays reachable under a `Docs` prefix: ``, ``, ``. See [Documenting a component library](https://shelldocs.shellui.dev/docs/guides/component-libraries.md). ## Inline tag or fence | Name | Type | Default | Description | |------|------|---------|-------------| | `Inline tag` | `content` | | Renders the component only. Markdown body. Use for callouts, cards, steps, tables that are part of the page. | | `razor:preview` | `example` | | Renders the component with Preview and Code tabs. Razor body. Use when the reader is learning the tag. | ## See also - [razor:preview](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md): The live example fence. - [Components reference](https://shelldocs.shellui.dev/docs/components/callout.md): Every primitive you can write into a page. - [Documenting a component library](https://shelldocs.shellui.dev/docs/guides/component-libraries.md): Registering your own components as tags. --- # Navigation > How ShellDocs builds the sidebar. meta.json, order, dividers, subsections, hidden pages. Source: https://shelldocs.shellui.dev/docs/authoring/navigation The sidebar, breadcrumb, and prev/next chrome all derive from a **navigation graph** built at startup by walking `content/docs/**`. You control ordering with `meta.json`; presence is automatic. ## The core rule **Files decide presence, `meta.json` decides order.** Drop a `.md` into a folder, it shows up. `meta.json` lists the slugs you want to control ordering for; anything not listed appears alphabetically after your explicit list. ## Basic `meta.json` Each folder can carry a `meta.json`: ```json { "title": "Getting Started", "pages": ["installation", "quick-start", "project-structure", "configuration"] } ``` | Name | Type | Default | Description | |------|------|---------|-------------| | `title` | `string?` | | Folder display name shown in the sidebar section header. Falls back to a title-cased folder name (getting-started → Getting Started). | | `pages` | `array` | | Ordered list of slug entries. Each entry is a slug, three dashes for a divider, or a subsection object. | | `hidden` | `string[]?` | | Slugs of pages / subfolders that should route (URLs resolve) but not appear in the sidebar. | ## Slug entries A slug is a file basename without `.md`, or a subfolder name : both resolve the same way. ```json { "pages": ["introduction", "getting-started", "components"] } ``` - `"introduction"` → `content/docs/introduction.md` - `"getting-started"` → `content/docs/getting-started/` (subfolder becomes a section) Don't give a page and a folder the same name in one directory. For a landing page at a folder's URL, put an `index.md` inside the folder. ## Dividers The literal string `"---"` becomes a section divider in the sidebar : a thin rule with no click affordance. ```json { "pages": [ "introduction", "---", "getting-started", "---", "components", "cli" ] } ``` Use dividers to group related pages without wrapping them in a nested section. ## Subsections An object entry with `title` and `pages` renders a nested labeled group without needing a real subfolder. Handy when the content lives under a flat structure but you want visual grouping. ```json { "pages": [ "introduction", { "title": "Guides", "pages": ["installation", "quick-start"] }, { "title": "Reference", "pages": ["components", "cli", "tokens"] } ] } ``` Subsection pages still resolve at their normal URLs : the subsection is a sidebar-only construct. ## Auto-append Files or folders not listed in a `meta.json` are **appended alphabetically after your explicit list**. So dropping a new `.md` into `content/docs/components/` shows it in the sidebar without editing `meta.json`. > **Ordering that scales** > > For the top of the sidebar, hand-order the important pages via `pages`; let everything else auto-append. You get intentional lead-in ordering + zero-effort discovery for new content. ## Auto-append order Within the auto-appended group: 1. **Subfolders first**, alphabetically by folder name. 2. **Pages after**, ordered by frontmatter `order` (ascending), then alphabetically by title as a tie-breaker. Set `order: N` in a page's frontmatter to control its position within the auto-appended group. ## No `meta.json` If a folder has no `meta.json`, everything auto-orders: 1. Subfolders first, alphabetically. 2. Pages after, `order` then alphabetical. ## Hidden pages `hidden` lists slugs (page or folder) that should **route** but not appear in the sidebar. Their URLs still resolve, and their content is still indexed by search : they just don't show up in navigation. ```json { "pages": ["introduction", "getting-started"], "hidden": ["landing", "archived"] } ``` Use cases: - Landing pages reached only through the package selector - Private drafts in-flight - Archived content kept for URL stability Hidden folder children are still indexed : the framework walks the tree and stores every URL, so deep links work. ## URLs | Name | Type | Default | Description | |------|------|---------|-------------| | `content/docs/introduction.md` | `→` | | /docs/introduction | | `content/docs/getting-started/installation.md` | `→` | | /docs/getting-started/installation | | `content/docs/guides/index.md` | `→` | | /docs/guides (index.md gets the folder URL) | ## Index pages An `index.md` in a folder is that folder's landing page. In the sidebar the section label links to it and is highlighted when it is the current page; the page is not listed a second time underneath. In a collapsible section the chevron is a separate button, so the label can be a link. List it first in the folder's `meta.json`, or leave it out and let it sort by `order`: ```json { "title": "Authoring", "pages": ["index", "frontmatter", "fenced-code"] } ``` ## Folder URLs redirect A folder with no `index.md` has no page of its own. Visiting its URL redirects to the first page inside it, so `/docs/components` goes to `/docs/components/callout` and `/docs` goes to the first page of the docs. Details and custom rules are in [Redirects](https://shelldocs.shellui.dev/docs/guides/redirects.md). ## `title` on a subfolder Set `title` in the folder's `meta.json` to control the sidebar section header. It falls back to a title-cased folder name, which is wrong for acronyms: a `cli` folder becomes "Cli". Set `"title": "CLI"` for those. ## Versions When docs versions are configured, the sidebar shows only the current version's pages, and prev/next and search stay inside it. See [Versioning](https://shelldocs.shellui.dev/docs/guides/versioning.md). ## See also - [Frontmatter](https://shelldocs.shellui.dev/docs/authoring/frontmatter.md): Per-page title, description, order. - [Redirects](https://shelldocs.shellui.dev/docs/guides/redirects.md): Folder URLs, version roots and moved pages. - [Versioning](https://shelldocs.shellui.dev/docs/guides/versioning.md): One docs tree per released version. - [ShellDocs.Core](https://shelldocs.shellui.dev/docs/packages/core.md): NavigationGraph API : programmatic traversal. --- # Callout > Info / warning / danger / tip / success box with icon, title, and body. Source: https://shelldocs.shellui.dev/docs/components/callout `Callout` is the workhorse admonition box : the primitive you reach for when a paragraph needs to say "heads up" without turning into a full section. Info, warning, danger, tip, success : each variant swaps the icon and the accent color. ## Basic use ```razor ShellDocs is in alpha. The primitive surface is stable enough to build real docs sites : expect small API polish before 1.0. ``` ## Variants Five variants, each with its own icon and color: ```razor Neutral, blue-toned. The default. ``` ```razor Green, lightbulb icon. Optional side quest, best-practice. ``` ```razor Amber, triangle icon. Might bite you if you skip it. ``` ```razor Red, X-in-circle icon. Will bite you. ``` ```razor Green. Same icon family as tip. Confirmation, completion. ``` > **Alias** > > `Variant="error"` is accepted as an alias for `Variant="danger"`. Same rendering. Use whichever reads better in context. ## Titleless `Title` is optional : drop it for a bare accent box. ```razor No title, just a line of body content next to the icon. ``` ## Text-only via attribute For very short, single-line callouts you can pass the body as a `Text` attribute instead of child content : useful when authoring nested inside another component tag where wrapping in a child body reads awkwardly. ```razor ``` `ChildContent` wins when both are set. ## Props | Name | Type | Default | Description | |------|------|---------|-------------| | `Variant` | `string` | `"info"` | One of info \| tip \| success \| warning \| danger \| error. Case-insensitive. | | `Title` | `string?` | `—` | Optional bolded title above the body. | | `Text` | `string?` | `—` | Short body content. Ignored when ChildContent is set. | | `ChildContent` | `RenderFragment?` | `—` | Body content. Supports full markdown when authored inline in a .md file. | ## Markdown in the body A `Callout` written straight into the page (an inline tag) takes a markdown body : inline code, emphasis, links, lists and fenced code all work. > **Inline tag, markdown body** > > Run `shelldocs dev`, then open **/docs/introduction**. See [Inline component tags](https://shelldocs.shellui.dev/docs/authoring/component-tags.md). Inside a `razor:preview` fence the body is parsed as Razor instead, so the source tab shows exactly what you would write in a `.razor` file. Use HTML there (``, ``, ``) : markdown syntax would render literally. ## Name collisions If your component library registers its own `Callout`, `` renders yours. ShellDocs' version stays reachable as ``. The same holds for every built-in primitive. ## When to use - Aside that's important enough to survive skimming - Warning about a footgun mid-code-walkthrough - Tip that unlocks the "why" behind a design choice - Confirmation that a step's expected side effect is correct ## When not to use - Ordinary prose emphasis : just write the sentence - Multi-paragraph rationale : promote it to a `## Note` section instead - Stacking three callouts in a row : reflow to a single one with structure inside ## See also - [Card](https://shelldocs.shellui.dev/docs/components/card.md): Visual grouping without an icon. - [Steps](https://shelldocs.shellui.dev/docs/components/steps.md): For sequential guidance : each step gets its own numbered rail. --- # Card > A titled, described tile: linkable, groupable, iconable. The building block behind CardGrid. Source: https://shelldocs.shellui.dev/docs/components/card `Card` is the visual grouping primitive. A titled panel with an optional description, an optional icon, and optional link behavior : renders as `` when you set `Href`, as a plain `
` otherwise. ## Basic use ```razor ``` ## As a link Setting `Href` renders the card as an anchor : the whole card becomes clickable. ```razor ``` Set `External="true"` to add `target="_blank"` and `rel="noopener"` : safe for outbound links. ```razor ``` ## With an icon Pass raw SVG markup as `IconSvg`. It renders inside a `.doc-card-icon` container above the title. ```razor ``` Write the SVG with single-quoted attributes so it fits inside the double-quoted `IconSvg` value. The same attribute works on a `Card` written straight into the page: - **Composable**: An inline card with an icon. Write the markup itself, not HTML entities : attribute values are not decoded. ## With child content Use `ChildContent` for anything richer than a description : lists, a code block, another primitive. ```razor Bring your own component library, register it in one line with RegisterComponentsFromAssembly, and use its tags in markdown. ``` ## In a grid `Card`'s natural habitat is inside `CardGrid`, which lays out any number of children on a responsive grid. ```razor ``` ## Props | Name | Type | Default | Description | |------|------|---------|-------------| | `Title` | `string?` | `—` | Optional title, rendered as .doc-card-title. | | `Description` | `string?` | `—` | Optional short description below the title. | | `Href` | `string?` | `—` | When set, the whole card becomes a link pointing here. | | `IconSvg` | `string?` | `—` | Raw SVG markup rendered above the title. Use single quotes inside it. | | `External` | `bool` | `false` | When true (and Href set), adds target='_blank' and rel='noopener'. | | `ChildContent` | `RenderFragment?` | `—` | Rich body content, rendered below the description. | ## See also - [CardGrid](https://shelldocs.shellui.dev/docs/components/card-grid.md): Responsive layout wrapper for a set of cards. - [LinkCard](https://shelldocs.shellui.dev/docs/components/link-card.md): Simpler variant : always a link, with a trailing arrow glyph. --- # CardGrid > Responsive grid wrapper for Card / LinkCard children. Source: https://shelldocs.shellui.dev/docs/components/card-grid `CardGrid` lays out a set of `Card` or `LinkCard` children on a CSS grid, responsive by default : the grid collapses to one column on narrow viewports. ## Basic use ```razor ``` ## Column counts `Columns` controls the desktop column count. Common values are `2`, `3`, `4`. The layout still collapses to one column on mobile regardless. ```razor ``` ## Mixing children `CardGrid` doesn't care what kind of card lives inside it : you can mix `Card` and `LinkCard`, or drop custom markup in. ```razor ``` ## Props | Name | Type | Default | Description | |------|------|---------|-------------| | `Columns` | `int` | `2` | Desktop column count. The CSS falls back to a single column on narrow viewports. | | `ChildContent` | `RenderFragment?` | `—` | Cards to lay out. | ## See also - [Card](https://shelldocs.shellui.dev/docs/components/card.md): The tile primitive. - [LinkCard](https://shelldocs.shellui.dev/docs/components/link-card.md): Always-a-link tile variant. --- # LinkCard > An always-clickable card with a trailing arrow glyph. For "next" tiles at the bottom of a page. Source: https://shelldocs.shellui.dev/docs/components/link-card `LinkCard` is `Card`'s "always a link" cousin. Renders as an `` with a trailing right-arrow glyph and a hover accent. Use it for cross-references and next-step tiles. ## Basic use ```razor ``` ## External Set `External="true"` for outbound links : adds `target="_blank"` and `rel="noopener"`. ```razor ``` ## In a grid The canonical use : pair with `CardGrid` for prev/next-style "keep reading" tiles at the bottom of a page. ```razor ``` ## Props | Name | Type | Default | Description | |------|------|---------|-------------| | `Href` (required) | `string` | `"#"` | Target URL. Required : LinkCard is always a link. | | `Title` | `string?` | `—` | Title, bold, primary color. | | `Description` | `string?` | `—` | Short muted description below the title. | | `External` | `bool` | `false` | When true, adds target='_blank' and rel='noopener'. | ## LinkCard vs Card | Name | Type | Default | Description | |------|------|---------|-------------| | `Always a link` | `LinkCard` | | Href is required. The trailing arrow signals click affordance visually. | | `Sometimes a link` | `Card` | | Href is optional : renders as a link when set, a plain block otherwise. No arrow glyph. | | `Rich body` | `Card` | | Card accepts ChildContent for lists, code blocks, nested components. LinkCard is title + description only. | | `Icon slot` | `Card` | | Only Card renders an IconSvg above the title. | ## See also - [Card](https://shelldocs.shellui.dev/docs/components/card.md): More flexible tile : optional link, optional icon, rich body. - [CardGrid](https://shelldocs.shellui.dev/docs/components/card-grid.md): Responsive grid wrapper. --- # Steps > Numbered ordered list with a vertical rail. For walk-throughs where sequence matters. Source: https://shelldocs.shellui.dev/docs/components/steps `Steps` wraps a set of `Step` children in an ordered list styled with a left-rail and centered numbered badges. Use it for install / setup / migration guides where the reader is meant to work top-to-bottom. ## Basic use ```razor Run dotnet tool install -g ShellDocs.CLI --prerelease. shelldocs init MyDocs produces docs/MyDocs.Docs/ with a working site. shelldocs dev hot-reloads on .md, .razor and .cs edits. ``` ## Titleless steps `Title` is optional : drop it for a bare numbered bullet. ```razor Fetch the changes. Run the migration. Verify the output. ``` ## Rich step bodies Written as an inline tag in the page, a `Step` takes a markdown body : fenced code, callouts, nested components. Leave a blank line between the tag and a fence. The rail extends through the full body. **1. Add the package** ```bash dotnet add package ShellDocs.Components --prerelease ``` **2. Register the services** Add a line to `Program.cs`: ```csharp builder.Services.AddShellDocs(); ``` **3. Verify** > **Nested primitives work** > > Callouts, cards, code groups : anything you can use in top-level prose also works inside a `Step`. The source for that block: ````markdown ```bash dotnet add package ShellDocs.Components --prerelease ``` Add a line to `Program.cs`: ```csharp builder.Services.AddShellDocs(); ``` ```` Use the inline form for real walk-throughs. Keep `razor:preview` for showing the component itself : fence bodies are Razor, so markdown inside them renders literally. ## Props ### Steps | Name | Type | Default | Description | |------|------|---------|-------------| | `ChildContent` | `RenderFragment?` | `—` | The Step children. | ### Step | Name | Type | Default | Description | |------|------|---------|-------------| | `Title` | `string?` | `—` | Optional title, rendered next to the numbered badge. | | `ChildContent` | `RenderFragment?` | `—` | Step body. Markdown when written as an inline tag, Razor inside a preview fence. | ## When to use - Multi-step install / setup instructions - Migration guides - "Here's what to do next" walk-throughs where each step depends on the previous ## When not to use - Independent bullet list : use `-` or `*` - Cross-referencing a decision matrix : use `TypeTable` - One or two items : plain prose reads better than ceremony ## See also - [Callout](https://shelldocs.shellui.dev/docs/components/callout.md): Sidebars mid-step for warnings and tips. - [FileTree](https://shelldocs.shellui.dev/docs/components/file-tree.md): Show the file layout that results from a step. --- # Tabs > Keyboard-navigable tabs for parallel content, with sync groups that follow the reader across pages. Source: https://shelldocs.shellui.dev/docs/components/tabs `Tabs` splits parallel content into panels the reader switches between : the same instructions per platform, the same task in two tools. The strip and every panel are rendered on the server and `shelldocs.js` does the switching, so tabs work on static hosts with no Blazor runtime. ## Basic use Wrap two or more `Tab` elements. `Label` is the text in the strip; the tag body is the panel. ```razor Runs on a .NET host with a live connection per visitor. Prerendered HTML served from any static host. Hot reload on markdown, Razor and C# edits. ``` ## Default tab The first tab is selected unless `DefaultValue` names another one, by `Label` or by `Value`. ```razor winget install Microsoft.DotNet.SDK.10 brew install --cask dotnet-sdk sudo apt-get install -y dotnet-sdk-10.0 ``` ## Sync groups Give several `Tabs` the same `SyncKey` and picking a tab in one switches all of them. The choice is saved in `localStorage`, so it also applies on other pages and on the next visit. ```razor Source: GitHub Actions. Build output directory: publish. Custom domain via a CNAME file in the output. Custom domain from the dashboard. ``` Use stable keys (`os`, `host`, `language`). The key names the saved preference, so renaming it resets readers' choice. Sync matches tabs by `Value`, or `Label` when there is no `Value` : give tabs that mean the same thing the same value across groups. ## Markdown in panels Written as an inline tag in the page, each `Tab` takes a markdown body. Leave a blank line between the tags and any fence. **Run** ```bash shelldocs dev --port 5200 ``` Watches `.md`, `.razor` and `.cs` files. **Publish** ```bash shelldocs build --site-url https://docs.example.com ``` Writes a static site to `./publish`. For panels that are only code, [`CodeGroup`](https://shelldocs.shellui.dev/docs/components/code-group.md) is the same mechanism with a code-block frame. ## Keyboard and accessibility The strip is a `role="tablist"`. Each button is a `role="tab"` with `aria-selected` and `aria-controls`, and each panel is a `role="tabpanel"` labelled by its tab. Only the selected tab is in the tab order. With a tab focused: - **Arrow Left / Arrow Right** select the previous or next tab, wrapping around. - **Home / End** jump to the first or last tab. - **Tab** moves on to the content after the strip. ## Props ### Tabs | Name | Type | Default | Description | |------|------|---------|-------------| | `DefaultValue` | `string?` | `first tab` | Label or Value of the tab selected on first render. | | `SyncKey` | `string?` | `—` | Groups with the same key switch together and remember the choice in localStorage. | | `Class` | `string?` | `—` | Extra classes on the outer element. | | `ChildContent` | `RenderFragment?` | `—` | The Tab children. | ### Tab | Name | Type | Default | Description | |------|------|---------|-------------| | `Label` (required) | `string` | `—` | Text shown in the strip. | | `Value` | `string?` | `Label` | Stable identifier used by DefaultValue and sync. | | `ChildContent` | `RenderFragment?` | `—` | Panel content. | ## Styling The parts carry stable classes: `.sd-tabs` (outer element, with `data-tabs-value` set to the selected value), `.sd-tabs-list` (strip), `.sd-tabs-trigger` (each button, `aria-selected="true"` when selected) and `.sd-tabs-panel`. ## Name collisions If your component library also registers a `Tabs`, `` renders yours. This one stays available as `` and ``. ## See also - [CodeGroup](https://shelldocs.shellui.dev/docs/components/code-group.md): Tabs with a code-block frame. - [Deployment](https://shelldocs.shellui.dev/docs/guides/deployment.md): What works on a static host and what needs a server. --- # CodeGroup > Tabbed set of code blocks with site-wide sync via SyncKey. For "same thing in three package managers." Source: https://shelldocs.shellui.dev/docs/components/code-group `CodeGroup` wraps a set of `CodeTab` children in a tabbed panel with a code-block frame. Give several groups the same `SyncKey` and they switch together, on every page, and remember the choice between visits. ## Basic use **dotnet CLI** ```bash dotnet add package ShellDocs.Components --prerelease ``` **PackageReference** ```xml ``` Write it as an inline tag in the page, with a fenced block in each tab. Leave a blank line between the tags and the fences: ````markdown ```bash dotnet add package ShellDocs.Components --prerelease ``` ```xml ``` ```` ## Syncing tabs Set the same `SyncKey` on every group that should stay in step. Pick a tab in either group below and the other follows: **dotnet CLI** ```bash dotnet add package ShellDocs.Core --prerelease ``` **PackageReference** ```xml ``` **dotnet CLI** ```bash dotnet add package ShellDocs.Markdown --prerelease ``` **PackageReference** ```xml ``` > **The choice follows the reader** > > `SyncKey="pkg"` is the convention on this site for install snippets. The selection is saved in `localStorage`, so it carries to other pages and to the next visit. ## Rich tab content Tab bodies are markdown, so a tab can hold several code blocks, prose, or other components. **With the CLI** ```bash shelldocs init MyDocs cd docs/MyDocs.Docs shelldocs dev ``` **By hand** ```bash dotnet new blazor -o MyDocs.Docs cd MyDocs.Docs dotnet add package ShellDocs.Components --prerelease ``` Then wire up `Program.cs` and `App.razor` : see [attach mode](https://shelldocs.shellui.dev/docs/cli/init.md). ## Props ### CodeGroup | Name | Type | Default | Description | |------|------|---------|-------------| | `SyncKey` | `string?` | `—` | Groups with the same key switch together, across pages, and remember the choice in localStorage. | | `ChildContent` | `RenderFragment?` | `—` | The CodeTab children. Must be direct children. | ### CodeTab | Name | Type | Default | Description | |------|------|---------|-------------| | `Label` (required) | `string` | `—` | Tab label. Also the value sync matches on : tabs with the same label in the same sync group switch together. | | `ChildContent` | `RenderFragment?` | `—` | Tab body. | ## How it works The tab strip and every panel are rendered on the server. `shelldocs.js` switches panels through `data-tabs`, `data-tab-target` and `data-tab-panel` attributes, so a `CodeGroup` works on a static host with no Blazor runtime. It follows the WAI-ARIA tabs pattern : arrow keys move between tabs, Home and End jump to the first and last. Sync matches tabs by `Label`. Two groups sharing a key only move together on labels they have in common. `CodeGroup` and [`Tabs`](https://shelldocs.shellui.dev/docs/components/tabs.md) share this mechanism. Use `Tabs` when the panels are not code. ## See also - [Tabs](https://shelldocs.shellui.dev/docs/components/tabs.md): The same tab mechanism for any content. - [Fenced code](https://shelldocs.shellui.dev/docs/authoring/fenced-code.md): Everything ShellDocs does with fenced code blocks. --- # FileTree > Recursive folder/file diagram. For showing project layout without a screenshot. Source: https://shelldocs.shellui.dev/docs/components/file-tree `FileTree` and `FileTreeItem` render a nested folder-and-file diagram with glyphs, comments, and highlights : the docs-friendly answer to "show me the layout." Fully static markup, no JS. ## Basic use ```razor ``` ## Comments `Comment` renders a muted annotation after the file name : great for calling out what a file is for. ```razor ``` ## Highlight Set `Highlight="true"` on any item to give it an accent background : useful for pointing at what a step created or what the reader should focus on. ```razor ``` ## Deep nesting There's no depth cap : nest as far as needed. The glyph size and indentation stay consistent. ```razor ``` ## Props ### FileTree | Name | Type | Default | Description | |------|------|---------|-------------| | `ChildContent` | `RenderFragment?` | `—` | FileTreeItem children forming the root list. | ### FileTreeItem | Name | Type | Default | Description | |------|------|---------|-------------| | `Name` (required) | `string` | | File or folder name displayed on the row. | | `IsFolder` | `bool` | `false` | When true, renders a folder glyph and allows nested FileTreeItem children. | | `Comment` | `string?` | `—` | Muted annotation displayed after the name. | | `Highlight` | `bool` | `false` | Emphasizes the row with an accent background. | | `ChildContent` | `RenderFragment?` | `—` | Nested items. Only rendered when IsFolder is true. | ## See also - [Project structure](https://shelldocs.shellui.dev/docs/getting-started/project-structure.md): Full walkthrough of a scaffolded ShellDocs site : uses FileTree top-to-bottom. - [Steps](https://shelldocs.shellui.dev/docs/components/steps.md): Pair FileTree with Steps to show what each step creates. --- # TypeTable > A four-column props / API table. What every "Props" section on this site is built with. Source: https://shelldocs.shellui.dev/docs/components/type-table `TypeTable` renders a compact four-column table : Prop, Type, Default, Description : with a `Required` chip on rows that need it. It's the primitive every "Props" or "API" section on this site is built with. ## Basic use ```razor ``` ## Required rows Set `Required="true"` on a `TypeRow` to add the "Required" chip next to the prop name. ```razor ``` ## Not-just-for-props The columns are labeled Prop / Type / Default / Description but nothing forces "type" to be a C# type. Use it wherever a four-column labelled-list would help : CLI commands, package families, framework primitives. ```razor ``` ## Rows with the same name Rows are independent, so two can share a `Name` : overloads of a method, or one option described for two cases. ```razor ``` ## Empty state If you render a `TypeTable` with no rows, it emits a single "No props documented." row rather than an empty grid : keeps the layout stable. ## How it works `TypeRow` doesn't render its own markup : it registers itself with the parent `TypeTable` via a cascading parameter in `OnInitialized`. The parent collects the row info and renders the table body. Consequence: rows must be direct children of `TypeTable` : wrapping them in a `
` breaks the cascading resolution. ## Props ### TypeTable | Name | Type | Default | Description | |------|------|---------|-------------| | `ChildContent` | `RenderFragment?` | `—` | The TypeRow children. Must be direct descendants. | ### TypeRow | Name | Type | Default | Description | |------|------|---------|-------------| | `Name` (required) | `string` | | First column : prop / command / key name. Rendered as inline code. | | `Type` | `string` | | Second column : type / category. Rendered as inline code when set, em-dash otherwise. | | `Default` | `string?` | `—` | Third column : default value or fallback. Em-dash when empty. | | `Description` | `string?` | `—` | Fourth column : free-text description. | | `Required` | `bool` | `false` | When true, appends a Required chip to the Name cell. | ## See also - [AutoTypeTable](https://shelldocs.shellui.dev/docs/components/auto-type-table.md): Generate the props table for a component from its code. - [CodeGroup](https://shelldocs.shellui.dev/docs/components/code-group.md): Tabbed code blocks : same discipline of columns, different axis. --- # AutoTypeTable > A props table generated from a component's parameters, defaults and XML doc comments. Source: https://shelldocs.shellui.dev/docs/components/auto-type-table `AutoTypeTable` builds a props table for a registered component by reflecting on it. You name the component; the table lists every `[Parameter]` with its type, default and description. It replaces copying each prop into a `TypeRow` by hand, and it can't drift from the code. ## Basic use ```markdown ``` | Name | Type | Default | Description | |------|------|---------|-------------| | `Columns` | `int` | `2` | Number of grid columns on desktop. | | `Count` | `int` | `4` | How many packages to list, in dependency order. | That table comes from this component: ```razor @code { /// Number of grid columns on desktop. [Parameter] public int Columns { get; set; } = 2; /// How many packages to list, in dependency order. [Parameter] public int Count { get; set; } = 4; } ``` ## Where each column comes from | Name | Type | Default | Description | |------|------|---------|-------------| | `Prop` | `name` | | Every public property marked with the Parameter attribute. EditorRequired adds the Required chip. | | `Type` | `C# type` | | Written compactly, with nullability and generic arguments. | | `Default` | `value` | | From a DefaultValue attribute if present, otherwise read from a new instance of the component. Empty strings and render fragments show as a dash. | | `Description` | `text` | | The property's XML doc summary. | ## Turning on descriptions Descriptions are read from the XML documentation file next to the assembly. Enable it in the project that owns the components: ```xml true $(NoWarn);CS1591 ``` `CS1591` is the "missing XML comment" warning. Suppress it unless you want a warning for every undocumented member. Without an XML file the table still renders, with a dash in the description column. A component library from NuGet shows descriptions when its package ships the `.xml` file. ## Generic components A generic component lists its type parameters first, marked required, then its props. Defaults are read from an instance closed over `object` when the constraints allow it. ## Unknown component If `Component` doesn't match a registered type, the table shows a single row saying so. ## AutoTypeTable or TypeTable | Name | Type | Default | Description | |------|------|---------|-------------| | `AutoTypeTable` | `generated` | | Component props. Always matches the code. | | `TypeTable` | `hand-written` | | Anything that isn't a component: CLI flags, options, tokens. Also when you want wording that differs from the doc comments. | ## Props | Name | Type | Default | Description | |------|------|---------|-------------| | `Component` (required) | `string` | `—` | Tag name of a registered component. | ## See also - [TypeTable](https://shelldocs.shellui.dev/docs/components/type-table.md): The hand-written table. - [DemoPreview](https://shelldocs.shellui.dev/docs/components/demo-preview.md): Show the component next to its table. --- # PreviewFrame > The frame around every live example. Preview and Code tabs, a copy button, an actions menu, and three layouts. Source: https://shelldocs.shellui.dev/docs/components/preview-frame `PreviewFrame` is the frame around every live example on a ShellDocs site. A `razor:preview` fence, a `DemoPreview` and a `ComponentPreview` all render through it, so they share one toolbar and one set of layouts. > **You rarely write PreviewFrame yourself** > > Author examples as `razor:preview` fences and the framework wraps them. Reach for `PreviewFrame` directly only when you build a custom page outside `DocsPage`. ## The toolbar ```razor Switch to the Code tab above to see the source for this box. ``` | Name | Type | Default | Description | |------|------|---------|-------------| | `Preview \| Code` | `tabs` | | Preview shows the live component. Code shows the highlighted source. Arrow keys, Home and End move between them. | | `Copy` | `button` | | Copies the source exactly as written. A check mark confirms the copy. | | `Open in new tab` | `menu` | | Opens the page at this example's anchor. | | `Report a bug` | `menu` | | Opens a new issue pre-filled with the example name and page URL. | | `Suggest something` | `menu` | | Same, labelled as a suggestion. | The two issue links point at `ShellDocsOptions.IssueTrackerUrl`, which defaults to the new-issue page of `GitHubRepo`. With neither set they are hidden. The toolbar is driven by `shelldocs.js`, so it works on static hosts. A tab picked before the Blazor circuit starts is kept when the circuit takes over. ## Layouts A frame centres its example by default. Two other layouts cover examples that don't suit that. | Name | Type | Default | Description | |------|------|---------|-------------| | `center` | `default` | | Centres the example and shrinks it to its content. Right for buttons, badges, small cards. | | `stretch` | `opt-in` | | Block-level components fill the frame's width. Right for tables, inputs, charts, tabs. | | `scroll` | `opt-in` | | An example wider than the frame scrolls inside it on small screens. Right for toolbars and pagination. A scroll box clips popovers, so only use it when needed. | On a fence, add the layout after the language: ````markdown ```razor:preview stretch ``` ```` ```razor ``` On `DemoPreview` and `ComponentPreview`, set `Layout="stretch"` or `Layout="scroll"`. On phones the frame's side padding shrinks, and an example that is still too wide starts at the left edge instead of being cut off on both sides. ## Anchors Every frame has a stable id, used by *Open in new tab* and safe to link to. | Name | Type | Default | Description | |------|------|---------|-------------| | `preview-1, preview-2, …` | `fence` | | razor:preview fences, numbered in page order. | | `demo-{component}` | `DemoPreview` | | From the component name. Override with the Id parameter when a page shows the same demo twice. | | `example-{component}-{hash}` | `ComponentPreview` | | The hash comes from the source, so two previews of one component get different ids. | ## Page typography stays out A frame carries the `not-prose` class. Page styles for paragraphs, lists, headings and links skip it, so a component inside a frame looks the way it does in a real app. Add `not-prose` to any element of your own that should be left alone. ## Errors When a fence names a component that isn't registered, the frame shows a red panel naming the tag and pointing at the registration call, instead of falling back to a plain code block. ```razor ``` Attributes that can't be applied are skipped with a logged warning and the component still renders. That covers event callbacks, `@bind-*`, `@ref`, values that can't be converted to the parameter's type, and unknown attributes on a component with no catch-all. ## Props | Name | Type | Default | Description | |------|------|---------|-------------| | `Preview` | `PreviewSlot?` | `—` | A parsed razor:preview fence. Built by the markdown pipeline. | | `Content` | `RenderFragment?` | `—` | Content mode: what to render in the Preview tab. | | `Code` | `string?` | `—` | Content mode: the source shown in the Code tab. | | `Language` | `string` | `razor` | Highlighting language for the Code tab. | | `Title` | `string?` | `—` | Optional title in the frame header. | | `Layout` | `string?` | `center` | center, stretch or scroll. | | `Id` | `string?` | `—` | Anchor id. Must be the same on every render of the page. | | `Name` | `string?` | `component name` | Used in issue titles. | | `Error` | `string?` | `—` | Shows the error panel with this message instead of the example. | | `ErrorTitle` | `string` | `razor:preview error` | Heading of the error panel. | ## See also - [razor:preview](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md): How to write the fence. - [DemoPreview](https://shelldocs.shellui.dev/docs/components/demo-preview.md): Examples that need a code block of their own. - [ComponentPreview](https://shelldocs.shellui.dev/docs/components/component-preview.md): A preview written as a tag. - [Configuration](https://shelldocs.shellui.dev/docs/getting-started/configuration.md): IssueTrackerUrl and the other options. --- # DemoPreview > Shows a demo component from your docs project with its .razor file as the source. Source: https://shelldocs.shellui.dev/docs/components/demo-preview `DemoPreview` renders a demo component from your docs project and shows that component's `.razor` file in the Code tab. Use it for examples a `razor:preview` fence can't express : anything that needs a `@code` block, state, a loop, or injected services. ## Basic use ```markdown ``` **Package family** ```razor @foreach (var package in Packages.Take(Count)) { } @code { /// Number of grid columns on desktop. [Parameter] public int Columns { get; set; } = 2; /// How many packages to list, in dependency order. [Parameter] public int Count { get; set; } = 4; private record Package(string Name, string Summary); private static readonly Package[] Packages = [ new("ShellDocs.Components", "Layouts, chrome and the content primitives."), new("ShellDocs.Markdown", "The Markdig pipeline and component slots."), new("ShellDocs.Core", "Navigation graph and search index."), new("ShellDocs.Tokens", "Design tokens as CSS variables."), new("ShellDocs.CLI", "init, add, dev and build."), ]; } ``` The Code tab shows `PackageFamilyDemo.razor` as it is on disk, `@code` block included. The reader copies a complete, working component. ## Setting it up **1. Put demos in a folder** Any folder in the docs project works. This site uses `Components/Demos`. ```text Components/ Demos/ PackageFamilyDemo.razor ``` **2. Register the demos and point at their source** ```csharp builder.Services.AddShellDocs(o => { o.RegisterComponentsFromAssembly("MyDocs.Components.Demos"); o.DemoSourceRoot = Path.Combine(builder.Environment.ContentRootPath, "Components", "Demos"); }); ``` `DemoSourceRoot` is searched recursively for a file named after the component. **3. Ship the source files** The Razor SDK leaves `.razor` files out of publish output. Copy them with a `None` item so the source is there after `dotnet publish` and `shelldocs build`: ```xml ``` ## Interactive demos and static hosts A demo with `@onclick` handlers needs a running Blazor app. On a static export the demo renders in its initial state and doesn't respond. Demos that only compute what to render, like the one above, work everywhere. See [Deployment](https://shelldocs.shellui.dev/docs/guides/deployment.md). ## Code for something other than the demo By default the Code tab shows the demo's `.razor` file. Put `` children inside the tag and it shows those instead, one tab each : the same example in another syntax, or for another platform. `SyncKey` switches them together across the site and `Caption` adds a note to the toolbar. See [Code tabs on previews](https://shelldocs.shellui.dev/docs/components/code-tabs.md). ## The same demo twice The frame's anchor is `demo-{component}`. When a page shows one demo more than once, give each its own `Id`: ```markdown ``` ## Errors A red panel replaces the example when the component isn't registered, when `DemoSourceRoot` isn't set, or when no matching `.razor` file is found under it. The message never includes the server path. ## Props | Name | Type | Default | Description | |------|------|---------|-------------| | `Component` (required) | `string` | `—` | Name of the registered demo component, and of its .razor file. | | `Title` | `string?` | `—` | Title in the frame header. | | `Layout` | `string?` | `center` | center, stretch or scroll. | | `Id` | `string?` | `demo-{component}` | Anchor id for the frame. | | `SyncKey` | `string?` | `—` | Sync key for its code tabs. | | `Caption` | `string?` | `PreviewCaption` | Muted note in the toolbar. | | `CodeTabs` | `IReadOnlyList?` | `—` | Code tabs, when the preview is written in a .razor file. In markdown, use CodeTab children. | ## See also - [razor:preview](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md): For examples that are plain markup. - [AutoTypeTable](https://shelldocs.shellui.dev/docs/components/auto-type-table.md): Generate the props table for the same component. - [Code tabs on previews](https://shelldocs.shellui.dev/docs/components/code-tabs.md): Show code for several targets in the Code panel. - [IframePreview](https://shelldocs.shellui.dev/docs/components/iframe-preview.md): A live example that is another page, not a Blazor component. --- # ComponentPreview > A live preview written as a tag. Names the component in an attribute and passes the rest through. Source: https://shelldocs.shellui.dev/docs/components/component-preview `ComponentPreview` is a preview written as a tag instead of a fence. The target component goes in `Component`, every other attribute is forwarded to it, and the Code tab shows the tag you would write in Razor. > **Prefer the fence** > > A `razor:preview` fence reads better in the page source and keeps your formatting in the Code tab. Use `ComponentPreview` when a preview has to sit inside another tag's body, such as a `Step` or a `Tab`, where a fence would need blank lines around it. ## Basic use **Hello** ```razor Body text written on the tag. ``` ```markdown Body text written on the tag. ``` ## Fence or tag | Name | Type | Default | Description | |------|------|---------|-------------| | `Code tab` | `fence` | | Shows the fence body exactly as written, including indentation and comments. | | `Code tab` | `tag` | | Rebuilt from the component name, the attributes in alphabetical order, and the body. | | `Several components` | `fence` | | A fence can hold sibling tags and wrapper elements. | | `One component` | `tag` | | A tag previews exactly one component. | ## Forwarded attributes Attributes other than `Component` and `Layout` are forwarded to the target, converted to each parameter's type the same way a fence converts them. A value that can't be converted is skipped with a logged warning and the component still renders. **Forwarded** ```razor ``` ## Child content The body is parsed as Razor, like a fence body, so nested tags stay where you put them and nothing is wrapped in a paragraph. Direct child tags that match one of the target's `RenderFragment` parameters go into that slot. ## Generic components Close a generic target with a type-parameter attribute, as in Razor: ```markdown ``` See [razor:preview](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md) for the accepted type spellings. ## Layout `Layout="stretch"` lets a block-level target fill the frame. `Layout="scroll"` lets a wide one scroll on small screens. See [PreviewFrame](https://shelldocs.shellui.dev/docs/components/preview-frame.md). ## Unknown component When `Component` doesn't match a registered type, the frame shows an error panel naming the tag. Register the assembly: ```csharp builder.Services.AddShellDocs(o => { o.RegisterComponentsFromAssembly(); }); ``` ## Code tabs `` children replace the generated tag in the Code panel. They are taken out of the body first, so the target component never receives them as child content. `SyncKey` and `Caption` work as on `DemoPreview`. See [Code tabs on previews](https://shelldocs.shellui.dev/docs/components/code-tabs.md). ## Props | Name | Type | Default | Description | |------|------|---------|-------------| | `Component` (required) | `string` | `—` | Tag name of the component to preview. | | `Layout` | `string?` | `center` | center, stretch or scroll. | | `SyncKey` | `string?` | `—` | Sync key for its code tabs. | | `Caption` | `string?` | `PreviewCaption` | Muted note in the toolbar. | | `ChildContent` | `RenderFragment?` | `—` | Passed to the target as its child content. | | `other attributes` | `any` | `—` | Forwarded to the target component. | ## See also - [PreviewFrame](https://shelldocs.shellui.dev/docs/components/preview-frame.md): The frame, its toolbar and layouts. - [DemoPreview](https://shelldocs.shellui.dev/docs/components/demo-preview.md): Previews of components that need their own code block. --- # Code tabs on previews > Replace a preview's Code panel with tabs you write, switch them together across the site, and add a page-level switch and a caption. Source: https://shelldocs.shellui.dev/docs/components/code-tabs A preview's Code panel normally shows the Razor that rendered it. Put `` children inside the preview and the panel shows those instead, one tab each. Use it when the reader's code isn't the code that drew the example : the same result in two syntaxes, or one live example documented for several platforms. **Markdown** ```markdown ``` **Razor** ```razor @foreach (var package in Packages) { } ``` Written as: ````markdown ```markdown ``` ```razor @foreach (var package in Packages) { } ``` ```` ## The rules - Each tab's code is the first fence inside it. The fence's language drives highlighting. - Without a fence, the tag's text is the code and `Language="…"` sets the language. - The **Copy** button copies the tab that is showing. - Without `` children the Code panel shows the Razor source, as before. ## Where they work | Name | Type | Default | Description | |------|------|---------|-------------| | `DemoPreview` | `yes` | | The tabs replace the demo's .razor file in the Code panel. | | `ComponentPreview` | `yes` | | The tabs are taken out of the body before it is passed to the component as child content. | | `IframePreview` | `yes` | | Without tabs an iframe preview has no Code panel at all. | | `razor:preview fence` | `no` | | The fence body is Razor, so a code fence can't sit inside it. Use DemoPreview instead. | ## Switching together `SyncKey` works as it does on [`CodeGroup`](https://shelldocs.shellui.dev/docs/components/code-group.md) and [`Tabs`](https://shelldocs.shellui.dev/docs/components/tabs.md). Every block with the same key switches together, on this page and on later ones, and the choice is saved in `localStorage`. ## A missing tab When a block has no tab for the current choice it says so and keeps its height, instead of jumping to another tab. Choose **Razor** in the first example, then look at this one: **Markdown** ```markdown ``` ## Page switch A page switch is a row of tabs on the page itself that drives a `SyncKey`. The **Markdown | Razor** row at the top of this page is one. Declare it once and it appears on every page whose blocks use that key: ```csharp builder.Services.AddShellDocs(o => { o.AddSyncSwitch("platform", "MAUI", "Avalonia"); }); ``` It sits under the page header when `RenderPageTitle` is on, otherwise right above the first block that uses the key. Pages that don't use the key don't get one. The first option is the default until a reader picks another, so every block starts on the same tab. To choose the spot yourself, write the tag. The automatic one is then left out on that page: ```markdown ``` For a key with no `AddSyncSwitch`, list the options on the tag. This page does that: ```markdown ``` | Name | Type | Default | Description | |------|------|---------|-------------| | `SyncKey` (required) | `string` | `—` | The key the switch drives. | | `Options` | `string?` | `from AddSyncSwitch` | Comma-separated option labels. They must match the tab labels. | ## Caption `Caption` adds a short muted note to the preview's toolbar, such as which platform rendered the example. The first preview on this page has one. ```markdown ``` `ShellDocsOptions.PreviewCaption` sets a caption for every preview on the site. A preview's own `Caption` replaces it. ```csharp o.PreviewCaption = "Rendered with Blazor"; ``` ## From a Razor file Outside markdown, pass the tabs as a parameter: ```razor ``` A `PreviewCodeTab` is a label, the code, and a language that defaults to `text`. ## XAML `xaml` and `axaml` fences are highlighted as XML. Sites created before `0.1.13-alpha` need `'xml'` in the `langs` list of the Shiki loader in `App.razor`. ## Props These are the same on `DemoPreview`, `ComponentPreview` and `IframePreview`. | Name | Type | Default | Description | |------|------|---------|-------------| | `SyncKey` | `string?` | `—` | Blocks with the same key switch together and remember the choice. | | `Caption` | `string?` | `PreviewCaption` | Muted note in the toolbar. | | `CodeTabs` | `IReadOnlyList?` | `—` | The tabs, when the preview is written in a .razor file. | `CodeTab` inside a preview: | Name | Type | Default | Description | |------|------|---------|-------------| | `Label` (required) | `string` | `—` | Tab label, and the value a SyncKey matches on. | | `Language` | `string?` | `text` | Highlighting language when the body has no fence. | ## See also - [DemoPreview](https://shelldocs.shellui.dev/docs/components/demo-preview.md): The preview these tabs most often sit in. - [IframePreview](https://shelldocs.shellui.dev/docs/components/iframe-preview.md): A live example from another page, with the same tabs. --- # IframePreview > Shows another page in the Preview panel, with the same toolbar, code tabs and theme as the rest of the site. Source: https://shelldocs.shellui.dev/docs/components/iframe-preview `IframePreview` shows another page inside a preview frame. Use it for a live example built with something other than Blazor : a WebAssembly build of a desktop UI toolkit, a plain HTML page, an embedded playground. The frame keeps the same toolbar as every other preview and can carry [code tabs](https://shelldocs.shellui.dev/docs/components/code-tabs.md). ## Basic use ```markdown ``` **A page in a frame** [Live preview](https://shelldocs.shellui.dev/demos/theme-frame.html) The page in the frame is a static file in this site's `wwwroot`. Switch the site's theme and it follows. ## With code tabs An iframe preview has no Razor source to show, so its Code panel is hidden until you give it tabs. **Markdown** ```markdown ``` **Script** ```js window.addEventListener('message', e => { if (e.data?.type === 'shelldocs-theme') applyTheme(e.data.theme); }); ``` ## Loading Each iframe starts its own page, and a heavy one starts its own app. `Lazy` decides when. | Name | Type | Default | Description | |------|------|---------|-------------| | `visible` | `default` | | Loads when the frame scrolls into view. | | `click` | `Lazy` | | Shows a Run live preview button and loads when the reader presses it. | | `none` | `Lazy` | | Loads with the page. | With `Lazy="click"`, nothing loads until the reader asks: ```markdown ``` [Live preview](https://shelldocs.shellui.dev/demos/theme-frame.html?c=click) ## Theme The page in the frame is told the site's theme twice: - `?theme=light` or `?theme=dark` is added to its URL when it loads. - A message is posted to it whenever the reader switches theme: `{ type: "shelldocs-theme", theme: "light" | "dark" }`. The page reads both: ```js const params = new URLSearchParams(location.search); applyTheme(params.get('theme') === 'dark' ? 'dark' : 'light'); window.addEventListener('message', e => { if (e.data?.type === 'shelldocs-theme') applyTheme(e.data.theme); }); ``` ## Static hosts The loading, the theme parameter and the theme message are handled by `shelldocs.js`, so an iframe preview behaves the same on a static export. The page in the frame has to be reachable there too: a file under `wwwroot` is copied into the export by `shelldocs build`. ## Props | Name | Type | Default | Description | |------|------|---------|-------------| | `Src` (required) | `string` | `—` | The page to show. | | `Height` | `string` | `320` | Pixels, or any CSS length such as 24rem. | | `Lazy` | `string` | `visible` | visible, click or none. | | `Title` | `string?` | `—` | Title in the frame header. | | `Id` | `string?` | `iframe-{hash of Src}` | Anchor id for the frame. Set it when a page shows the same Src twice. | | `SyncKey` | `string?` | `—` | Sync key for its code tabs. | | `Caption` | `string?` | `PreviewCaption` | Muted note in the toolbar. | | `CodeTabs` | `IReadOnlyList?` | `—` | The tabs, when the preview is written in a .razor file. | ## See also - [Code tabs on previews](https://shelldocs.shellui.dev/docs/components/code-tabs.md): CodeTab, SyncKey, the page switch and captions. - [DemoPreview](https://shelldocs.shellui.dev/docs/components/demo-preview.md): For live examples that are Blazor components. --- # Documenting a component library > Register your own Blazor components, preview them live, and generate their props tables. Source: https://shelldocs.shellui.dev/docs/guides/component-libraries ShellDocs is built to document Blazor components with the components themselves on the page. This guide takes a library from "referenced by the docs project" to live previews with generated props tables. ## Reference and register Reference the library from the docs project, then register its components so their tags work in markdown. ```csharp builder.Services.AddShellDocs(o => { o.RegisterComponentsFromAssembly(); }); ``` The type argument is any type from the assembly. Every public, concrete component in it is registered under its class name. | Call | Registers | |---|---| | `RegisterComponentsFromAssembly()` | Every public component in the marker's assembly | | `RegisterComponentsFromAssembly("MyLib.Forms")` | Only components whose namespace starts with the prefix | | `RegisterComponentsFromAssembly(t => …)` | Only components the filter accepts | | `RegisterComponent ``` ```` Enum values can be written the way you would in Razor. Event callbacks and `@bind` attributes are kept in the Code tab and skipped at render, so a realistic example does not break the page. The full rules are in [razor:preview](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md). ## Examples with state An example that needs a `@code` block can't be a fence. Write it as a `.razor` file in the docs project and show it with `DemoPreview`, which renders the component and uses the file as the source. ```csharp o.RegisterComponentsFromAssembly("MyDocs.Components.Demos"); o.DemoSourceRoot = Path.Combine(builder.Environment.ContentRootPath, "Components", "Demos"); ``` ```markdown ``` Setup details are in [DemoPreview](https://shelldocs.shellui.dev/docs/components/demo-preview.md). ## Generate the props table `AutoTypeTable` reads a component's parameters, defaults and XML doc comments. ```markdown ``` For descriptions to appear, the library has to produce an XML documentation file and ship it next to the assembly. See [AutoTypeTable](https://shelldocs.shellui.dev/docs/components/auto-type-table.md). ## When names collide Component libraries often have a `Card`, `Tabs` or `Callout` of their own. ShellDocs ships primitives with those names too. **Your component wins.** `` in markdown renders your library's `Card`. ShellDocs' primitive stays reachable under a `Docs` prefix: | Name | Type | Default | Description | |------|------|---------|-------------| | `Card` | `your library` | | The tag resolves to the component you registered. | | `DocsCard` | `ShellDocs` | | Always the built-in primitive. Every built-in has a Docs alias. | A collision is logged at startup so you know it happened. If two of your own components claim one tag name, that is logged as a warning and the later registration wins. When you document a library that shadows built-ins, use the `Docs` names for page furniture (`DocsCallout`, `DocsSteps`, `DocsTabs`) so the page keeps working whichever component the short name points to. ## Generic components A generic component registers under its bare name and is closed per use with a type-parameter attribute: ```razor ``` If the library has both `Select` and a generic `Select` with a type parameter, the short tag stays with the non-generic one. ## Styling the previews A preview renders your component inside the docs page, so the page needs your library's stylesheet. Link it in `App.razor` after the ShellDocs stylesheets. ```html ``` Preview frames carry the `not-prose` class, so the docs page's typography doesn't reach into your component. If your library switches theme by toggling a `dark` class on the `html` element, ShellDocs follows it: `shelldocs.js` watches the class and keeps its own stored theme in step. ## Linking issues to examples Each preview's menu has *Report a bug* and *Suggest something*. They open a new issue pre-filled with the example and page. They point at your `GitHubRepo` unless you set a different tracker: ```csharp o.IssueTrackerUrl = "https://github.com/my-org/my-lib/issues/new"; ``` ## See also - [razor:preview](https://shelldocs.shellui.dev/docs/authoring/razor-preview.md): Everything the fence accepts. - [DemoPreview](https://shelldocs.shellui.dev/docs/components/demo-preview.md): Examples with their own code block. - [AutoTypeTable](https://shelldocs.shellui.dev/docs/components/auto-type-table.md): Generated props tables. - [Versioning](https://shelldocs.shellui.dev/docs/guides/versioning.md): Docs for each release of the library. --- # Versioning > Keep one docs tree per released version, with a version selector and chrome that stays inside the version you are reading. Source: https://shelldocs.shellui.dev/docs/guides/versioning ShellDocs can serve several versions of your docs side by side. Each version is a folder of content. Readers switch with a version selector, and the sidebar, search, breadcrumb and prev/next links stay inside the version they are reading. ## Lay out the content Give each version its own folder under `content/docs`. Pages that are the same for every version, such as a changelog, stay outside. ```text content/ docs/ changelog.md # Unversioned v2.0/ meta.json introduction.md cli/ v1.9.1/ meta.json introduction.md cli/ ``` A version folder is an ordinary content folder. Its `meta.json` orders its pages, and its `title` is the label shown where the folder is named. ## Register the versions ```csharp builder.Services.AddShellDocs(o => { o.AddVersion("v2.0", "v2.0.0", "/docs/v2.0", "Current stable", latest: true); o.AddVersion("v1.9.1", "v1.9.1", "/docs/v1.9.1", "Previous release"); }); ``` | Name | Type | Default | Description | |------|------|---------|-------------| | `id` (required) | `string` | | Identifier for the version. Replaces the version token in package root URLs. | | `label` (required) | `string` | | Text shown in the selector. | | `rootUrl` (required) | `string` | | URL of the version folder. | | `description` | `string?` | | Second line in the selector menu. | | `latest` | `bool` | `false` | Marks the version new readers land on. Shown with a latest badge. | ## Which version is current The current version is the one whose root URL is a prefix of the page's path, matched by whole segments. On a page outside every version folder, it is the version marked `latest`, or the first one registered when none is. ## What changes once versions exist | Name | Type | Default | Description | |------|------|---------|-------------| | `Version selector` | `chrome` | | Appears under the package selector in the sidebar, and in the header for the TopNav layout. Hidden with fewer than two versions. | | `Sidebar` | `scoped` | | Inside a version, lists only that version's pages. Outside one, version folders are left out because the selector covers them. | | `Prev / next` | `scoped` | | Never crosses a version boundary. | | `Search` | `scoped` | | Shows the current version plus pages outside every version. | | `Breadcrumb` | `scoped` | | Leaves the version folder out of the trail. | With no versions registered, none of this applies and the site behaves as before. ## Switching versions Each option in the selector is a plain link. It goes to the first of these that exists: 1. The same page in the target version. 2. The current package's root in the target version. 3. The target version's first page. Because the options are links, the selector works on a static export. ## Versioned packages If you use the package selector, put the `{version}` token in a package's root URL. It resolves to the current version's id, so switching package keeps the reader in the same version. ```csharp o.AddPackage("core", "MyLib.Core", "The core library.", "/docs/{version}/core"); o.AddPackage("cli", "MyLib.CLI", "The command line.", "/docs/{version}/cli"); ``` A package root that has no `index.md` links to its first page. ## Unversioned URLs A URL with no version in it that matches a page in the latest version redirects there: `/docs/cli/dev` goes to `/docs/v2.0/cli/dev`. Links from before you adopted versions keep working. A version root such as `/docs/v2.0` redirects to its first page. See [Redirects](https://shelldocs.shellui.dev/docs/guides/redirects.md). ## Dots in version names Folder names with dots, such as `v0.2.1`, keep their dots in every URL. One thing to know: a route written as `/docs/{*Path:nonfile}`, which is what `shelldocs init` scaffolds, does not match a URL whose last segment contains a dot. Pages under the version are fine. The bare root `/docs/v0.2.1` is handled by the redirect middleware before routing, so it works as long as redirects are enabled. ## Adding a new version **1. Copy the latest folder** Copy `content/docs/v2.0` to `content/docs/v2.1` and update the pages that changed. **2. Register it as latest** Add an `AddVersion` call for the new version with `latest: true` and remove the flag from the previous one. **3. Redirect anything you renamed** If a page moved or a version folder was renamed, add an `AddRedirect` rule so old links still land. ## See also - [Redirects](https://shelldocs.shellui.dev/docs/guides/redirects.md): Version roots, unversioned URLs and moved pages. - [Configuration](https://shelldocs.shellui.dev/docs/getting-started/configuration.md): AddVersion, AddPackage and the rest of the options. --- # Redirects > Folder URLs, version roots and moved pages redirect instead of returning a missing page. Source: https://shelldocs.shellui.dev/docs/guides/redirects Some URLs under your docs are not pages: a folder with no index, the root of a version, an address a page used to have. ShellDocs redirects those to the right page. Most of it is automatic; you add rules only for content you move. ## What redirects on its own | Name | Type | Default | Description | |------|------|---------|-------------| | `/docs` | `computed` | | Goes to the first page. Unversioned pages are preferred, then the latest version. | | `A folder with no index.md` | `computed` | | Goes to the first page inside it. On this site /docs/components goes to /docs/components/callout. | | `A version root` | `computed` | | Goes to that version's first page. | | `An unversioned URL` | `computed` | | When the same path exists in the latest version, goes there. /docs/cli/dev goes to /docs/v2.0/cli/dev. | Computed redirects answer `302`, because the target moves when you add a page or release a version. ## Rules for moved content Add a rule when you rename a page, move a folder, or rename a version. ```csharp builder.Services.AddShellDocs(o => { o.AddRedirect("/docs/setup", "/docs/getting-started/installation"); o.AddRedirect("/docs/v0.3.0", "/docs/v0.3"); }); ``` A rule matches by whole path segments and covers everything under it. The second rule above also sends `/docs/v0.3.0/intro` to `/docs/v0.3/intro`. Rules answer `301`. Pass `permanent: false` for a `302`: ```csharp o.AddRedirect("/docs/next", "/docs/v2.0", permanent: false); ``` ## How they are served `AddShellDocs` registers middleware that answers redirects before routing runs. That ordering is what makes a dotted version root like `/docs/v0.2.1` work, since the router would treat it as a file request. ## On a static export A static host can't answer with a redirect status, so `shelldocs build` writes a small HTML page at each source URL that sends the browser on with a meta refresh and includes a plain link as a fallback. The build output reports how many it wrote. ## Turning redirects off ```csharp o.EnableRedirects = false; ``` With redirects off, folder URLs and version roots show the not-found page, and `shelldocs build` writes no redirect pages. ## See also - [Versioning](https://shelldocs.shellui.dev/docs/guides/versioning.md): Where version roots and unversioned URLs come from. - [Navigation](https://shelldocs.shellui.dev/docs/authoring/navigation.md): Folders, index pages and meta.json. --- # Search > How search works on a .NET host and on a static export, and how to open it from your own markup or script. Source: https://shelldocs.shellui.dev/docs/guides/search Every ShellDocs site has search built in. The app builds the index, and the browser does the searching, so it works the same on a .NET host and on a static export. There is no service to sign up for and nothing to configure. ## Opening it - `Cmd+K` on macOS, `Ctrl+K` elsewhere. - The search field in the sidebar or header, and the search icon in the mobile bar. In the dialog, the arrow keys move through the results, `Enter` opens the highlighted one and `Escape` closes it. Results are ordinary links, so they can be opened in a new tab. ## What is searched | Name | Type | Default | Description | |------|------|---------|-------------| | `Pages` | `entry` | | Title, description, the sidebar section the page is in, and the body text. | | `Headings` | `entry` | | Every h2 and h3, linking straight to its anchor. | A match in the title ranks highest, then the section, the description and the body. Every word of the query has to match somewhere in the entry. A result that matched only in the body shows a short snippet around the match. On a site with [versions](https://shelldocs.shellui.dev/docs/guides/versioning.md), results come from the version the reader is on, plus any pages that sit outside every version. ## How it works **1. The app builds the index** At startup, from the navigation graph and the markdown files. **2. The app serves it as a file** At `/_shelldocs/search.json`. `AddShellDocs` sets this up; there is nothing to add to `Program.cs`. **3. The build copies it** `shelldocs build` saves the same file into the export as `_shelldocs/search.json`. **4. The browser ranks it** `shelldocs.js` fetches the file the first time search opens, then ranks it on every keystroke. Nothing is sent to a server. The file is found relative to the page's base href, so search also works for a site built with `--base-href`. ## Your own search button Any element with a `data-search-open` attribute opens search when clicked. ```html ``` Try it:
The built-in search buttons are wired the same way, so a custom header or home page needs no Blazor code for it. The dialog itself is part of `DocsLayout` and `HomeLayout`, so `Cmd+K` and `data-search-open` work on the home page too. Before `0.1.17-alpha`, `HomeLayout` had no dialog and a site had to add `` to its home page; remove that tag when you upgrade, or the page has two. ## From script ```js shelldocsSearch.open(); shelldocsSearch.close(); ``` ## Upgrading from before 0.1.15 > **Two things changed** > > `shelldocsSearch.init` is gone. If your own script called it, remove the call: search sets itself up. The `SearchState` service is still registered so existing code compiles, but nothing reads it any more. Open search with `data-search-open` or `shelldocsSearch.open()` instead. For search to work in a static export, both the packages the site references and the `ShellDocs.CLI` that builds it must be `0.1.15-alpha` or later. `shelldocs build` prints a warning when the site doesn't serve an index. ## See also - [Deployment](https://shelldocs.shellui.dev/docs/guides/deployment.md): What works on a static export and what needs a server. - [ShellDocs.Core](https://shelldocs.shellui.dev/docs/packages/core.md): The search index and the plain-text extraction behind it. --- # AI agents (llms.txt) > Serve the docs as llms.txt, every page as Markdown and instruction files for Claude Code, Cursor, Copilot and other coding agents. Source: https://shelldocs.shellui.dev/docs/guides/llms-txt `EnableLlmsTxt` publishes the docs in a form coding agents read well. It serves an [llms.txt](https://llmstxt.org) index, the whole site as one Markdown file, every page as Markdown, and instruction files an agent loads at startup. It also adds a menu by each page title for copying the page or opening it in a chat. This site has it on: try [`/llms.txt`](https://shelldocs.shellui.dev/llms.txt), or open the ⋯ menu next to this page's title. ## Turning it on Call `EnableLlmsTxt` in `AddShellDocs`. With no options it works as it is; this site sets three: ```csharp builder.Services.AddShellDocs(o => { o.SiteName = "ShellDocs"; o.SiteUrl = "https://shelldocs.shellui.dev"; o.EnableLlmsTxt(l => { l.Summary = "ShellDocs is a docs framework for .NET: Markdown pages, live Blazor component previews, search and a static export, in a Blazor Web App."; l.RulesFile = "Llms/agent-rules.md"; l.InstallCommandPattern = @"(?m)(?<=^[ \t]*)dotnet (?:tool install -g ShellDocs\.CLI|add package ShellDocs\.\w+)[^\r\n]*"; }); }); ``` Sites created with `shelldocs init` from `0.1.17-alpha` have the call already, with the options commented out. | Name | Type | Default | Description | |------|------|---------|-------------| | `Summary` | `string?` | `SiteTagline` | The quoted line under the site name at the top of llms.txt. | | `RulesFile` | `string?` | `—` | A Markdown file shown at the top of llms.txt and in every agent file, such as a list of rules for agents. A relative path is resolved against the app's content root. | | `InstallCommandPattern` | `string?` | `—` | A regular expression run over each page's code blocks. The first match is listed after the page in llms.txt as its install command. | | `AgentFiles` | `bool` | `true` | Serves AGENTS.md, CLAUDE.md, copilot-instructions.md and a Cursor rule. | | `PageActions` | `bool` | `true` | Shows the page actions menu by the title. | The rules file is read at runtime, so copy it to the output and publish folders. In the `.csproj`: ```xml ``` Links in the generated files are absolute. They start with `SiteUrl` when it is set, and with the address the request came in on when it isn't. ## The files | URL | Contents | |---|---| | `/llms.txt` | Site name, summary and rules, then every page with its description, grouped by sidebar section. | | `/llms-full.txt` | The same header, then every page as Markdown, in one file. | | `{page}.md` | One page as Markdown, at the page's URL plus `.md`. | | `{version root}/llms.txt`, `llms-full.txt` | The same two files for one version's pages. | | `/agents/AGENTS.md`, `CLAUDE.md`, `copilot-instructions.md`, `{site}.mdc` | Instruction files to put in a project. Also served under each version root. | On this site: [`/llms.txt`](https://shelldocs.shellui.dev/llms.txt), [`/llms-full.txt`](https://shelldocs.shellui.dev/llms-full.txt), and this page as [`/docs/guides/llms-txt.md`](https://shelldocs.shellui.dev/docs/guides/llms-txt.md). On a site with [versions](https://shelldocs.shellui.dev/docs/guides/versioning.md), the bare `/llms.txt` and `/llms-full.txt` cover the latest version and the pages outside every version. Each version root has its own pair. A site without versions has one pair covering every page. The running app serves all of them as `text/plain`, and the agent files as downloads. ## What the Markdown looks like Each page's Markdown is its source with the ShellDocs tags turned into plain Markdown: | Tag | Becomes | |---|---| | `razor:preview`, ``, `` | The example's source in a code fence. | | `` | A blockquote. Code fences inside it are kept. | | ``, `` | A bold label before each tab's content. | | ``, ``, `` | A list of titles, descriptions and links. | | `` | Numbered steps. | | `` | A text block. | | ``, `` | A Markdown table. | | Your own components | Their text content. | Links to other docs pages become absolute links to their `.md`, so an agent can follow them. Tags inside code fences are left as they are. ## The page actions menu The ⋯ button by the title of every docs page opens a menu: | Item | What it does | |---|---| | **Copy page** | Copies the page's Markdown to the clipboard. | | **View as Markdown** | Opens the page's `.md` in a new tab. | | **Open in Claude** | Starts a Claude chat that reads the page. | | **Open in ChatGPT** | Starts a ChatGPT chat that reads the page. | Both chats open with the prompt "Read {page}.md and help me with it.", where the address is the page's absolute `.md` URL. The menu works on a static export too. Set `PageActions = false` to hide it. ## Setting up an agent Each agent file is a `## {SiteName}` section that links `llms.txt` and includes the rules, so it can be added to the end of a file a project already has. For this site: **Claude Code** Claude Code reads `CLAUDE.md`. [Download CLAUDE.md](https://shelldocs.shellui.dev/agents/CLAUDE.md), or from the project root: ```bash curl -fsSL https://shelldocs.shellui.dev/agents/CLAUDE.md >> CLAUDE.md ``` **Cursor** Cursor reads rules from `.cursor/rules`. The rule is named after the site: [download shelldocs.mdc](https://shelldocs.shellui.dev/agents/shelldocs.mdc) into that folder. ```bash curl -fsSL --create-dirs -o .cursor/rules/shelldocs.mdc https://shelldocs.shellui.dev/agents/shelldocs.mdc ``` **GitHub Copilot** Copilot reads `.github/copilot-instructions.md`. [Download copilot-instructions.md](https://shelldocs.shellui.dev/agents/copilot-instructions.md): ```bash mkdir -p .github && curl -fsSL https://shelldocs.shellui.dev/agents/copilot-instructions.md >> .github/copilot-instructions.md ``` **Other agents** Codex and many other agents read `AGENTS.md`. [Download AGENTS.md](https://shelldocs.shellui.dev/agents/AGENTS.md): ```bash curl -fsSL https://shelldocs.shellui.dev/agents/AGENTS.md >> AGENTS.md ``` ## In a static export `shelldocs build` writes `llms.txt`, `llms-full.txt`, every page's `.md` and the agent files into the export, next to the pages. Their links use `--site-url`, then `SiteUrl`. With neither, they are root-relative, under `--base-href` when it is set. > **The host picks the content type** > > A static host serves the files with its own content types. Many send `.md` as `text/markdown`, which some browsers download instead of showing. Agents read either. Links on the site to `.txt` and `.md` files load the file, in the running app and in the export, instead of going through the docs router. ## See also - [Configuration](https://shelldocs.shellui.dev/docs/getting-started/configuration.md): Every option on ShellDocsOptions. - [shelldocs build](https://shelldocs.shellui.dev/docs/cli/build.md): Every step of the static export. - [Deployment](https://shelldocs.shellui.dev/docs/guides/deployment.md): Static export or a .NET host. - [Versioning](https://shelldocs.shellui.dev/docs/guides/versioning.md): Version roots and what each one covers. --- # Not-found page > DocsNotFound answers HTTP 404 for a URL with no page, and shelldocs build turns it into 404.html. Source: https://shelldocs.shellui.dev/docs/guides/not-found `DocsNotFound` is the view a reader gets for a docs URL that has no page. It keeps the site's layout, names the address that was asked for, and links back to the docs. ## Using it It goes in the `else` branch of `Components/Pages/DocsPage.razor`. `shelldocs init` writes it there for new sites. ```razor @if (_document is not null) { } else { } ``` A site created before `0.1.14-alpha` has a hand-written block in that branch. Replace it with the tag. ## What it does | Name | Type | Default | Description | |------|------|---------|-------------| | `Answers HTTP 404` | `status` | | A crawler or link checker sees a missing page as missing, not as a page that loaded. | | `Names the address` | `content` | | Shows the path that was requested. | | `Links back` | `content` | | To wherever /docs leads: the first docs page, in the latest version. | ## In a static export `shelldocs build` uses the same view in two ways. **It writes `404.html`.** The build requests a URL that doesn't exist and saves the response as `404.html` at the root of the export. GitHub Pages, Cloudflare Pages, Netlify and most other static hosts serve that file, with a 404 status, for any URL that has no file. One file serves every unknown URL, so the build leaves the address out of it and `shelldocs.js` fills in the path the reader asked for, relative to the site's base. Before `0.1.17-alpha` every unknown URL showed the build's own probe address. **It fails on a broken page.** If a page from the navigation graph answers 404 or renders `DocsNotFound`, the build stops with an error instead of publishing a not-found page under a real URL. The usual cause is content that didn't reach the publish output. > **--spa-fallback replaces 404.html** > > With `--spa-fallback`, `shelldocs build` overwrites `404.html` with a copy of the home page. A reader who follows a dead link then lands on the home page with no sign that the address was wrong. Leave the flag off unless you need it. ## Changing the text | Name | Type | Default | Description | |------|------|---------|-------------| | `Path` | `string?` | `the requested path` | The address shown in the message. | | `HomeHref` | `string?` | `where /docs leads` | Where the link back goes. | | `HomeLabel` | `string` | `Back to the docs` | Text of the link back. | ```razor ``` `DocsNotFound` is for `DocsPage.razor`. It is not available as a tag in markdown. ## See also - [shelldocs build](https://shelldocs.shellui.dev/docs/cli/build.md): Every step of the export, and the --spa-fallback flag. - [Redirects](https://shelldocs.shellui.dev/docs/guides/redirects.md): Send a moved page's old address to its new one. --- # Deployment > Publish as a static site or run on a .NET host. What works where, and a GitHub Pages workflow you can copy. Source: https://shelldocs.shellui.dev/docs/guides/deployment A ShellDocs site is a Blazor Web App. You can run it on a .NET host, or export it to static files with `shelldocs build` and serve those from any static host. This site runs on a .NET host, as a Docker image. ## Choosing | Name | Type | Default | Description | |------|------|---------|-------------| | `Static export` | `shelldocs build` | | Plain HTML, CSS and JS. Free or cheap hosting, nothing to keep running. Search works. Demos with event handlers render but do not respond. | | `A .NET host` | `dotnet publish` | | Everything works, including demos with event handlers. Needs a server or container that runs ASP.NET Core. | ## What works on a static export `shelldocs build` prerenders every page. The chrome is written so its initial state comes from the server and `shelldocs.js` handles the clicks, which means most of it needs no Blazor runtime. | Name | Type | Default | Description | |------|------|---------|-------------| | `Page content and navigation` | `works` | | Every page is a real HTML file. Links are ordinary links. | | `Sidebar sections` | `works` | | Expand and collapse. The section holding the current page starts open. | | `Package and version selectors` | `works` | | Menus open and close, options are links. | | `Table of contents` | `works` | | Highlights the heading in view. | | `Theme toggle` | `works` | | Remembers the choice in localStorage. | | `Tabs and CodeGroup` | `works` | | Including sync groups. | | `Preview frames` | `works` | | Preview and Code tabs, copy, the actions menu. | | `Mobile navigation` | `works` | | The drawer opens, closes on a link, the backdrop or Escape. | | `Redirects` | `works` | | Written as redirect pages. | | `Search` | `works` | | The index is a file in the export and is ranked in the browser. See below. | | `Collapsing the whole sidebar` | `works` | | The desktop collapse button. The choice is remembered across pages. | | `Not-found page` | `works` | | Written as 404.html, which most static hosts serve for unknown URLs. It names the address the reader asked for. | | `llms.txt and agent files` | `works` | | Written into the export as files, with the page actions menu working. See AI agents (llms.txt). | | `Demos with event handlers` | `needs a server` | | A demo that reacts to clicks renders in its initial state and does not respond. | ### Search The app serves its search index at `/_shelldocs/search.json`, and `shelldocs build` copies that file into the export. `shelldocs.js` fetches it the first time a reader opens search and ranks it in the browser, with the same scoring on a static host as on a .NET host. Nothing is sent to a server, and there is no external service to set up. More in [Search](https://shelldocs.shellui.dev/docs/guides/search.md). The site and the CLI both need to be on `0.1.15-alpha` or later. `shelldocs build` warns when the site's `ShellDocs.Components` is too old to serve an index. ### What still needs a server A demo with `@onclick` handlers, or any other Blazor event handler, needs a running app. On a static export it renders in its initial state and does not respond. If your docs depend on live demos like that, run the site on a .NET host. ## Building the static site ```bash dotnet tool install -g ShellDocs.CLI --prerelease shelldocs build --site-url https://docs.example.com ``` The output goes to `./publish`. Every flag is covered in [shelldocs build](https://shelldocs.shellui.dev/docs/cli/build.md). Two matter for hosting: - `--site-url` adds `sitemap.xml`, `robots.txt` and social-preview meta tags. - `--base-href` is needed only when the site is served from a subpath, such as a GitHub project page at `/my-repo/`. ## GitHub Pages This workflow builds on every push to `main` and can be run by hand. ```yaml name: Deploy to GitHub Pages on: push: branches: [main] workflow_dispatch: concurrency: group: pages cancel-in-progress: true permissions: pages: write id-token: write contents: read jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: global-json-file: global.json - run: dotnet tool install -g ShellDocs.CLI --version 0.1.17-alpha - run: shelldocs build --site-url https://docs.example.com - run: echo "docs.example.com" > publish/CNAME - run: touch publish/.nojekyll - uses: actions/upload-pages-artifact@v3 with: path: publish deploy: needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - id: deployment uses: actions/deploy-pages@v4 ``` Three lines are easy to miss: - **`.nojekyll`** : GitHub Pages runs Jekyll by default, and Jekyll drops folders that start with an underscore. Blazor's assets live in `_framework` and `_content`, so without this file the site loads with no styles or scripts. - **`CNAME`** : keeps a custom domain attached across deploys. Leave the step out if you use the default `github.io` address. - **Pin the CLI version** to the version of the ShellDocs packages your project references, so a new prerelease can't change the build under you. ### One-time setup **1. Set the Pages source** In the repository, open **Settings**, then **Pages**, and set **Source** to **GitHub Actions**. Until this is done the deploy job fails with a 404 from the Pages API. **2. Add the custom domain** On the same page enter the domain under **Custom domain** and save. **3. Point DNS at GitHub** At your DNS provider add a `CNAME` record for the subdomain with the value `your-org.github.io`. If the provider proxies traffic, turn the proxy off for this record until the certificate is issued. **4. Wait for the certificate, then enforce HTTPS** GitHub requests a certificate once the DNS check passes. When it arrives the **Enforce HTTPS** box becomes available. > **If the certificate never arrives, check CAA records** > > GitHub Pages certificates come from Let's Encrypt. A `CAA` record on the parent domain that names only another authority blocks it, with no error shown. This happens when another platform manages the apex domain and added its own record. Add a second record, `0 issue "letsencrypt.org"`, next to the existing one, then remove and re-add the custom domain in the Pages settings to restart the request. ### Project pages For a site served at `https://your-org.github.io/my-repo/`, drop the `CNAME` step and pass the subpath: ```bash shelldocs build --base-href /my-repo/ ``` ## Other static hosts Any host that serves a folder works: point it at `publish`. Many hosts' own build environments don't include the .NET SDK, so the dependable setup is to run `shelldocs build` in CI and upload the output, as the workflow above does. ## Running on a .NET host Publish and run it like any ASP.NET Core app. ```bash dotnet publish -c Release -o out dotnet out/MyDocs.Docs.dll ``` The content folder must be in the publish output. `shelldocs init` adds the project items that copy it: ```xml ``` Any platform that runs ASP.NET Core works: a container, a virtual machine, or a managed app service. ## See also - [shelldocs build](https://shelldocs.shellui.dev/docs/cli/build.md): Every flag and what the build does. - [Redirects](https://shelldocs.shellui.dev/docs/guides/redirects.md): How redirects are written into a static export. - [Search](https://shelldocs.shellui.dev/docs/guides/search.md): How search works without a server. - [Not-found page](https://shelldocs.shellui.dev/docs/guides/not-found.md): DocsNotFound and the 404.html the build writes. - [AI agents (llms.txt)](https://shelldocs.shellui.dev/docs/guides/llms-txt.md): The llms.txt files the build writes. --- # `shelldocs init` > Scaffold a fresh docs site OR attach ShellDocs to an existing Blazor project. Source: https://shelldocs.shellui.dev/docs/cli/init `shelldocs init` is the "get to running docs" command. Two modes: **create** (default) makes a fresh Blazor Web App under `docs/.Docs/` and wires everything for you, **attach** (`--attach`) augments an existing Blazor project in place without touching its `Program.cs`. ## Synopsis ```bash shelldocs init [path] [--dir ] [--attach] [--yes] [--theme ] ``` ## Create mode (default) Scaffolds a fresh Blazor Web App and patches the template to be a working ShellDocs site : one command from empty directory to a running docs project. ```bash shelldocs init MyDocs ``` Produces `MyDocs/` in the current directory (or, when called with no `path`, `docs/.Docs/`). **1. Runs `dotnet new blazor -o `** Scaffolds a fresh Blazor Web App using the standard template. Fails fast if the target directory exists and isn't empty : use `--attach` instead of overwriting. **2. Adds package references** Injects `` and `` into the freshly-created `.csproj`. Both are pinned to the CLI's own version, so the scaffold always matches the tool that made it. **3. Adds content copy-to-output** Inserts a `` group so `dotnet publish` carries the markdown corpus into the publish output. **4. Writes starter content** Creates `content/docs/introduction.md` and `content/docs/meta.json` : the minimum a ShellDocs site needs to render. **5. Writes `Components/Pages/DocsPage.razor`** The single-page router that serves every `/docs/**` URL from the `.md` files under `content/`. You don't edit this : it's just a `` around a ``, with `` for a URL that has no page. **6. Strips the fresh-template chrome** Deletes `Counter.razor`, `Weather.razor`, `NavMenu.razor`, `MainLayout.razor.css`, `Home.razor.css`. Rewrites `MainLayout.razor` as a bare pass-through and replaces `Home.razor` with the ShellDocs welcome page. **7. Patches `Program.cs`** Adds `using ShellDocs.Components;`, `builder.WebHost.UseStaticWebAssets();`, and `builder.Services.AddShellDocs(...)` : anchoring on the exact template shape produced by `dotnet new blazor`. The `AddShellDocs` call turns on [llms.txt for AI agents](https://shelldocs.shellui.dev/docs/guides/llms-txt.md) with `o.EnableLlmsTxt(...)`, its options commented out. **8. Patches `Components/App.razor`** Adds the `_content/ShellDocs.Tokens/tokens.css` and component CSS links, the theme-bootstrap script, and `shelldocs.js` + Shiki loader. Also switches `` to `` so interactive components (theme toggle, search, tabs) come alive. **9. Registers with the nearest solution** Walks up from the working directory looking for a `.slnx` or `.sln` (stops at the repo root or 6 levels up). If one is found and doesn't already reference the project, runs `dotnet sln add`. > **Idempotent** > > Re-running `shelldocs init` on the same directory won't clobber your edits : every step checks for the target artifact and skips if it's already present. ### Path inference When called with no `path` argument, the target is `docs/.Docs/`. So running from `~/code/my-library/` produces `~/code/my-library/docs/my-library.Docs/`. ### Site name The header brand is derived from the target directory name, stripping a trailing `.Docs` suffix. `my-library.Docs` → header shows "my-library". ### GitHub prompt Interactive mode asks for `owner/repo` (blank to skip). The answer wires the GitHub link in the header and is used by search / social meta. Pass `--yes` to skip the prompt. ## Attach mode (`--attach`) Augments an existing Blazor project in place. Never touches your `Program.cs` : instead emits `SHELLDOCS_SETUP.md` with copy-paste snippets so you can wire ShellDocs into your custom middleware, auth, or DI without a merge conflict. ```bash shelldocs init --attach --dir path/to/my/blazor/project ``` **1. Verifies the target is a Blazor project** Checks the `.csproj` for `Microsoft.NET.Sdk.Web`, `.Razor`, `.BlazorWebAssembly`, or a `Microsoft.AspNetCore.Components` reference. Bails if none match. **2. Adds package references** Same as create mode. **3. Writes starter content** Same as create mode. **4. Writes DocsPage.razor** Same as create mode. **5. Writes SHELLDOCS_SETUP.md** A per-project setup guide with copy-paste snippets for the exact `AddShellDocs`, `MapShellDocs`, and `App.razor` additions your project needs. This is what create mode does automatically : attach mode leaves it to you. ## Options | Name | Type | Default | Description | |------|------|---------|-------------| | `path` | `argument` | | Target directory for the new docs project. Default: docs/{CwdName}.Docs. Ignored with --attach. | | `--dir` | `string` | `cwd` | Working directory. With --attach, the project directory to augment. | | `--attach` | `bool` | `false` | Attach mode : augment an existing Blazor project instead of creating one. | | `--yes` | `bool` | `false` | Non-interactive : skip the GitHub-repo prompt, take defaults everywhere. | | `--theme` | `string` | `shadcn` | Accepted values are shadcn, fuma and nextra. Has no effect in this release: every site gets the same tokens. | ## Exit codes | Name | Type | Default | Description | |------|------|---------|-------------| | `0` | `ok` | | Scaffolding completed (create) or attach patches wrote. | | `1` | `usage` | | Target directory exists and isn't empty (create), or target is not a Blazor project (attach), or missing/invalid arguments. | | `other` | `pass-through` | | dotnet new blazor exit code propagated (create mode). | ## See also - [shelldocs add](https://shelldocs.shellui.dev/docs/cli/add.md): Scaffold a starter page after init. - [Project structure](https://shelldocs.shellui.dev/docs/getting-started/project-structure.md): Tour of what init produced. --- # `shelldocs add` > Scaffold a starter .md page from a template into content/. Source: https://shelldocs.shellui.dev/docs/cli/add `shelldocs add` drops a starter `.md` page into your `content/` tree. Three templates, each pre-populated with the primitives you'd typically reach for : so you're editing content immediately instead of copy-pasting boilerplate. ## Synopsis ```bash shelldocs add