Skip to content
Gx
GitHub

Docs shell

The Gx docs shell: header, sidebar, table of contents, pagination, splash, 404 and search dialog.

<shell.Header site={shell.Config{
  Title:    "Deedbox docs",
  Version:  "v0.1.0",
  Links:    []shell.Link{{Label: "GitHub", Href: gx.URL("https://example.com/gx")}},
  EditBase: "https://example.com/edit/main/content/docs/",
}} />

Installation

Run the command in the app module.

Terminal
gx add docs-shell

The command writes these files.

File Path in the app
Header.fixtures.go ui/docs-shell/Header.fixtures.go
Header.gx ui/docs-shell/Header.gx
NotFound.fixtures.go ui/docs-shell/NotFound.fixtures.go
NotFound.gx ui/docs-shell/NotFound.gx
PageMeta.fixtures.go ui/docs-shell/PageMeta.fixtures.go
PageMeta.gx ui/docs-shell/PageMeta.gx
Pagination.fixtures.go ui/docs-shell/Pagination.fixtures.go
Pagination.gx ui/docs-shell/Pagination.gx
SearchDialog.fixtures.go ui/docs-shell/SearchDialog.fixtures.go
SearchDialog.gx ui/docs-shell/SearchDialog.gx
Shell.fixtures.go ui/docs-shell/Shell.fixtures.go
Shell.gx ui/docs-shell/Shell.gx
Sidebar.fixtures.go ui/docs-shell/Sidebar.fixtures.go
Sidebar.gx ui/docs-shell/Sidebar.gx
SidebarGroup.fixtures.go ui/docs-shell/SidebarGroup.fixtures.go
SidebarGroup.gx ui/docs-shell/SidebarGroup.gx
SidebarItem.fixtures.go ui/docs-shell/SidebarItem.fixtures.go
SidebarItem.gx ui/docs-shell/SidebarItem.gx
Splash.fixtures.go ui/docs-shell/Splash.fixtures.go
Splash.gx ui/docs-shell/Splash.gx
ThemeSelect.fixtures.go ui/docs-shell/ThemeSelect.fixtures.go
ThemeSelect.gx ui/docs-shell/ThemeSelect.gx
Toc.fixtures.go ui/docs-shell/Toc.fixtures.go
Toc.gx ui/docs-shell/Toc.gx
fixtures.go ui/docs-shell/fixtures.go
helpers.go ui/docs-shell/helpers.go
nav.go ui/docs-shell/nav.go

Copy each file to its path in the app. Change each import of a registry package to the path of that package in the app.

ui/docs-shell/Header.fixtures.go
package shell

import "github.com/alternayte/gx"

var HeaderFixtures = gx.Fixtures[HeaderProps]{
	"Default": {Site: fixtureSite},
}
ui/docs-shell/Header.gx
package shell

props {
  // Site is the site-wide data: the title, the version and the header links.
  Site Config
}

<header class="gx-header sticky top-0 z-40 border-b border-border bg-background/95 backdrop-blur">
  <div class="mx-auto flex max-w-7xl items-center gap-4 px-4 py-3">
    <button type="button" class="gx-menu-button rounded-md border border-border px-2 py-1 text-sm lg:hidden" data-gx-menu aria-expanded="false" aria-controls="gx-sidebar">Menu</button>
    <a class="gx-site-title font-semibold" href={gx.URL("/")}>{p.Site.Title}</a>
    if p.Site.Version != "" {
      <span class="gx-version rounded-full border border-border px-2 py-0.5 text-xs text-muted-foreground" data-gx-version>{p.Site.Version}</span>
    }
    <div class="ml-auto flex items-center gap-3 text-sm">
      <button type="button" class="gx-search-button rounded-md border border-border px-2 py-1 text-muted-foreground" data-gx-search-open aria-haspopup="dialog">Search <kbd class="ml-1 text-xs">Ctrl K</kbd></button>
      for _, l := range p.Site.Links {
        <a class="gx-header-link" href={l.Href}>{l.Label}</a>
      }
      <ThemeSelect />
    </div>
  </div>
</header>
ui/docs-shell/NotFound.fixtures.go
package shell

import "github.com/alternayte/gx"

var NotFoundFixtures = gx.Fixtures[NotFoundProps]{
	"Default": {},
}
ui/docs-shell/NotFound.gx
package shell

props {
  // Title is the heading of the page.
  Title   string = "Page not found"
  // Message is the text below the title.
  Message string = "The page does not exist. Check the address or return to the start."
  // Home is the target of the Go home link.
  Home    string = "/"
}

<div class="gx-not-found mx-auto my-16 max-w-lg text-center">
  <p class="text-5xl font-bold">404</p>
  <h1 class="mt-4 text-2xl font-semibold">{p.Title}</h1>
  <p class="mt-2 text-muted-foreground">{p.Message}</p>
  <a class="mt-6 inline-flex items-center rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground" href={gx.URL(p.Home)}>Go home</a>
</div>
ui/docs-shell/PageMeta.fixtures.go
package shell

import "github.com/alternayte/gx"

var PageMetaFixtures = gx.Fixtures[PageMetaProps]{
	"Default": {Site: fixtureSite, Page: fixturePage},
	"Plain":   {Site: Config{Title: "Site"}, Page: Page{Title: "Page", Path: "/page/"}},
}
ui/docs-shell/PageMeta.gx
package shell

props {
  // Site is the site-wide data. Its EditBase builds the edit link; empty hides the link.
  Site Config
  // Page is the data of the current page: its path and its last-updated text.
  Page Page
}

<div class="gx-page-meta mt-8 flex flex-wrap gap-4 text-sm text-muted-foreground">
  if p.Site.EditURL(p.Page) != "" {
    <a class="gx-edit-link" href={gx.URL(p.Site.EditURL(p.Page))}>Edit this page</a>
  }
  if p.Page.Updated != "" {
    <span class="gx-updated" data-gx-updated>Last updated {p.Page.Updated}</span>
  }
</div>
ui/docs-shell/Pagination.fixtures.go
package shell

import "github.com/alternayte/gx"

var PaginationFixtures = gx.Fixtures[PaginationProps]{
	"Both":  {Prev: &NavItem{Label: "Start", Href: gx.URL("/start/")}, Next: fixtureNext},
	"Next":  {Next: fixtureNext},
	"Empty": {},
}
ui/docs-shell/Pagination.gx
package shell

props {
  // Prev is the previous sidebar item. Nil omits the link.
  Prev *NavItem
  // Next is the next sidebar item. Nil omits the link.
  Next *NavItem
}

if p.Prev != nil || p.Next != nil {
  <nav class="gx-pagination mt-10 flex justify-between gap-4 border-t border-border pt-4 text-sm" aria-label="Pages">
    if p.Prev != nil {
      <a class="gx-pagination-prev" href={p.Prev.Href} rel="prev">Previous: {p.Prev.Label}</a>
    } else {
      <span></span>
    }
    if p.Next != nil {
      <a class="gx-pagination-next" href={p.Next.Href} rel="next">Next: {p.Next.Label}</a>
    }
  </nav>
}
ui/docs-shell/SearchDialog.fixtures.go
package shell

import "github.com/alternayte/gx"

var SearchDialogFixtures = gx.Fixtures[SearchDialogProps]{
	"Default": {},
}
ui/docs-shell/SearchDialog.gx
package shell

<dialog id="gx-search" class="gx-search-dialog w-full max-w-lg rounded-lg border border-border bg-background p-0 text-foreground" data-gx-search data-gx-search-src="/pagefind/pagefind.js" aria-label="Search">
  <form method="get" action="/search" class="flex items-center gap-2 p-3" data-gx-search-form>
    <input type="search" name="q" class="gx-search-input w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm" placeholder="Search the docs" data-gx-search-input autocomplete="off" />
    <button type="submit" class="rounded-md bg-primary px-3 py-2 text-sm text-primary-foreground">Search</button>
    <button type="button" class="gx-search-close rounded-md border border-border px-3 py-2 text-sm" data-gx-search-close aria-label="Close">Close</button>
  </form>
  <div class="gx-search-results max-h-80 overflow-y-auto px-3 pb-3 text-sm" data-gx-search-results></div>
</dialog>
ui/docs-shell/Shell.fixtures.go
package shell

import "github.com/alternayte/gx"

var ShellFixtures = gx.Fixtures[ShellProps]{
	"Default": {Site: fixtureSite, Nav: fixtureNav, Page: fixturePage, Children: gx.Text("Body.")},
}
ui/docs-shell/Shell.gx
package shell

props {
  // Site is the site-wide data: the title, the version and the header links.
  Site     Config
  // Nav is the sidebar model: the groups and their items.
  Nav      Nav
  // Page is the data of the current page: its path, its headings and its previous and next
  // items.
  Page     Page
  // Children is the content of the page.
  Children gx.Node
}

<gx.Head bodyClass="bg-background text-foreground antialiased" />
<a class="gx-skip-link sr-only focus:not-sr-only focus:absolute focus:left-2 focus:top-2 focus:z-50 focus:rounded-md focus:bg-primary focus:px-3 focus:py-2 focus:text-primary-foreground" href="#gx-main">Skip to content</a>
<div class="gx-shell min-h-screen">
  <Header site={p.Site} />
  <div class="mx-auto flex max-w-7xl gap-8 px-4">
    <Sidebar nav={p.Nav} path={p.Page.Path} />
    <main id="gx-main" class="min-w-0 flex-1 py-8">
      <article class="gx-content" data-pagefind-body>{p.Children}</article>
      <Pagination prev={p.Page.Prev} next={p.Page.Next} />
      <PageMeta site={p.Site} page={p.Page} />
    </main>
    <Toc headings={p.Page.ShownTOC()} />
  </div>
</div>
<SearchDialog />
ui/docs-shell/Sidebar.fixtures.go
package shell

import "github.com/alternayte/gx"

var SidebarFixtures = gx.Fixtures[SidebarProps]{
	"Default": {Nav: fixtureNav, Path: "/guides/routing/pages/"},
}
ui/docs-shell/Sidebar.gx
package shell

props {
  // Nav is the sidebar model: the groups and their items.
  Nav  Nav
  // Path is the site path of the current page.
  // A nested item that holds the page starts open.
  Path string
}

<aside id="gx-sidebar" class="gx-sidebar w-64 shrink-0 border-r border-border" data-gx-sidebar>
  <nav aria-label="Docs" class="sticky top-16 max-h-[calc(100vh-4rem)] overflow-y-auto py-6 pr-4">
    for _, g := range p.Nav.Groups {
      <SidebarGroup group={g} path={p.Path} />
    }
  </nav>
</aside>
ui/docs-shell/SidebarGroup.fixtures.go
package shell

import "github.com/alternayte/gx"

var SidebarGroupFixtures = gx.Fixtures[SidebarGroupProps]{
	"Default": {Group: fixtureStart, Path: "/start/"},
	"Nested":  {Group: fixtureGuides, Path: "/guides/routing/pages/"},
}
ui/docs-shell/SidebarGroup.gx
package shell

props {
  // Group is the sidebar section: its label, its badge and its items.
  // A group with Collapsed starts closed.
  Group NavGroup
  // Path is the site path of the current page.
  // A nested item that holds the page starts open.
  Path  string
}

<details class="gx-nav-group mb-4" open={!p.Group.Collapsed}>
  <summary class="gx-nav-group-label flex cursor-pointer items-center gap-2 py-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
    {p.Group.Label}
    if p.Group.Badge != "" {
      <span class="gx-nav-badge rounded-full border border-border px-1.5 text-[10px]">{p.Group.Badge}</span>
    }
  </summary>
  <ul class="mt-1 list-none space-y-0.5 p-0">
    for _, it := range p.Group.Items {
      <SidebarItem item={it} path={p.Path} />
    }
  </ul>
</details>
ui/docs-shell/SidebarItem.fixtures.go
package shell

import "github.com/alternayte/gx"

var SidebarItemFixtures = gx.Fixtures[SidebarItemProps]{
	"Link":   {Item: NavItem{Label: "Routing", Href: gx.URL("/guides/routing/")}, Path: "/guides/routing/"},
	"Nested": {Item: fixtureRouting, Path: "/guides/routing/pages/"},
}
ui/docs-shell/SidebarItem.gx
package shell

props {
  // Item is the sidebar link. An item with nested items shows as a collapsible list.
  Item NavItem
  // Path is the site path of the current page.
  // A nested item that holds the page starts open.
  Path string
}

<li class="gx-nav-item">
  if len(p.Item.Items) > 0 {
    <details class="gx-nav-sub" open={ActiveItem(p.Path, p.Item)}>
      <summary class="gx-nav-sub-label cursor-pointer py-1 text-sm text-muted-foreground">
        {p.Item.Label}
        if p.Item.Badge != "" {
          <span class="gx-nav-badge ml-1 rounded-full border border-border px-1.5 text-[10px]">{p.Item.Badge}</span>
        }
      </summary>
      <ul class="mt-1 list-none space-y-0.5 border-l border-border p-0 pl-3">
        for _, child := range p.Item.Items {
          <SidebarItem item={child} path={p.Path} />
        }
      </ul>
    </details>
  } else {
    <a class="gx-nav-link block py-1 text-sm text-muted-foreground hover:text-foreground [&[aria-current=page]]:font-medium [&[aria-current=page]]:text-foreground" href={p.Item.Href} active="page">
      {p.Item.Label}
      if p.Item.Badge != "" {
        <span class="gx-nav-badge ml-1 rounded-full border border-border px-1.5 text-[10px]">{p.Item.Badge}</span>
      }
    </a>
  }
</li>
ui/docs-shell/Splash.fixtures.go
package shell

import "github.com/alternayte/gx"

var SplashFixtures = gx.Fixtures[SplashProps]{
	"Default": {Title: "Deedbox", Tagline: "Event sourcing in .NET.", Actions: gx.Text("Docs")},
	"Plain":   {Title: "Deedbox"},
}
ui/docs-shell/Splash.gx
package shell

props {
  // Title is the heading of the splash.
  Title    string
  // Tagline is the text below the title. Empty omits it.
  Tagline  string = ""
  // Actions is the row of links or buttons below the tagline. Nil omits it.
  Actions  gx.Node = nil
  // Children is the content below the actions. Nil omits it.
  Children gx.Node = nil
}

<header class="gx-splash my-12 text-center">
  <h1 class="m-0 text-5xl font-bold tracking-tight">{p.Title}</h1>
  if p.Tagline != "" {
    <p class="mx-auto mt-4 max-w-2xl text-lg text-muted-foreground">{p.Tagline}</p>
  }
  if p.Actions != nil {
    <div class="mt-6 flex flex-wrap items-center justify-center gap-3">{p.Actions}</div>
  }
  if p.Children != nil {
    <div class="gx-splash-body mt-10">{p.Children}</div>
  }
</header>
ui/docs-shell/ThemeSelect.fixtures.go
package shell

import "github.com/alternayte/gx"

var ThemeSelectFixtures = gx.Fixtures[ThemeSelectProps]{
	"Default": {},
}
ui/docs-shell/ThemeSelect.gx
package shell

<div class="gx-theme inline-flex gap-1 rounded-md border border-border p-0.5" role="group" aria-label="Theme">
  <button type="button" class="gx-theme-button rounded px-2 py-0.5 text-xs" data-gx-theme="light" aria-pressed="false">Light</button>
  <button type="button" class="gx-theme-button rounded px-2 py-0.5 text-xs" data-gx-theme="dark" aria-pressed="false">Dark</button>
  <button type="button" class="gx-theme-button rounded px-2 py-0.5 text-xs" data-gx-theme="auto" aria-pressed="false">Auto</button>
</div>
ui/docs-shell/Toc.fixtures.go
package shell

import "github.com/alternayte/gx"

var TocFixtures = gx.Fixtures[TocProps]{
	"Default": {Headings: fixtureHeadings},
}
ui/docs-shell/Toc.gx
package shell

props {
  // Headings holds the entries of the table of contents. Empty renders nothing.
  Headings []Heading
}

if len(p.Headings) > 0 {
  <nav class="gx-toc hidden w-56 shrink-0 xl:block" data-gx-toc aria-label="On this page">
    <div class="sticky top-16 max-h-[calc(100vh-4rem)] overflow-y-auto py-8 text-sm">
      <p class="mb-2 font-medium">On this page</p>
      <ul class="list-none space-y-1 border-l border-border p-0">
        for _, h := range p.Headings {
          <li class="gx-toc-item" data-level={h.Level}>
            <a class="gx-toc-link block pl-3 text-muted-foreground hover:text-foreground [&[aria-current=location]]:text-foreground [&[data-level='3']]:pl-6" href={gx.URL("#" + h.ID)} data-gx-toc-target={h.ID} data-level={h.Level}>{h.Text}</a>
          </li>
        }
      </ul>
    </div>
  </nav>
}
ui/docs-shell/fixtures.go
package shell

import "github.com/alternayte/gx"

// fixtureRouting is the nested item of the shell fixtures.
var fixtureRouting = NavItem{Label: "Routing", Href: gx.URL("/guides/routing/"), Items: []NavItem{
	{Label: "Pages", Href: gx.URL("/guides/routing/pages/")},
}}

// fixtureStart and fixtureGuides are the open groups of the shell fixtures.
var fixtureStart = NavGroup{Label: "Start", Items: []NavItem{
	{Label: "Introduction", Href: gx.URL("/start/"), Badge: "New"},
}}

var fixtureGuides = NavGroup{Label: "Guides", Badge: "12", Items: []NavItem{fixtureRouting}}

// fixtureNav is the sidebar model of the shell fixtures (REQ-CNT-06).
var fixtureNav = Nav{Groups: []NavGroup{
	fixtureStart,
	fixtureGuides,
	{Label: "Errors", Collapsed: true, Items: []NavItem{
		{Label: "GX1000", Href: gx.URL("/errors/GX1000/")},
	}},
}}

// fixtureSite is the site config of the shell fixtures.
var fixtureSite = Config{
	Title:    "Deedbox docs",
	Version:  "v0.1.0",
	Links:    []Link{{Label: "GitHub", Href: gx.URL("https://example.com/gx")}},
	EditBase: "https://example.com/edit/main/content/docs/",
}

// fixtureNext is the next page of the shell fixtures.
var fixtureNext = &NavItem{Label: "Routing", Href: gx.URL("/guides/routing/")}

// fixtureHeadings are the headings of the shell fixtures. Each one is in
// the default depth of the table of contents.
var fixtureHeadings = []Heading{
	{Level: 2, Text: "Install", ID: "install"},
	{Level: 3, Text: "First page", ID: "first-page"},
}

// fixturePage is the page data of the shell fixtures.
var fixturePage = Page{
	Title:   "Introduction",
	Path:    "/start/",
	Section: "Start",
	Updated: "Oct 2, 2026",
	Prev:    nil,
	Next:    fixtureNext,
	TOC:     fixtureHeadings,
}
ui/docs-shell/helpers.go
package shell

import "strings"

// Active reports whether href is the current page or a parent of it
// (REQ-CNT-06). The sidebar marks active items on the server.
func Active(path string, href string) bool {
	if href == "" || path == "" {
		return false
	}
	if path == href {
		return true
	}
	return len(path) > len(href) && path[:len(href)] == href && path[len(href)-1] == '/'
}

// ActiveItem reports whether any descendant of item is active.
func ActiveItem(path string, item NavItem) bool {
	if Active(path, string(item.Href)) {
		return true
	}
	for _, child := range item.Items {
		if ActiveItem(path, child) {
			return true
		}
	}
	return false
}

// HasTOC reports whether the page has headings to show.
func (p Page) HasTOC() bool {
	for _, h := range p.TOC {
		if h.Text != "" {
			return true
		}
	}
	return false
}

// TocDepth returns the heading depth limit of the page.
func (p Page) TocDepth() int {
	if p.Depth <= 0 {
		return 3
	}
	return p.Depth
}

// ShownTOC returns the headings the table of contents shows.
func (p Page) ShownTOC() []Heading {
	depth := p.TocDepth()
	var out []Heading
	for _, h := range p.TOC {
		if h.Level >= 2 && h.Level <= depth && h.Text != "" {
			out = append(out, h)
		}
	}
	return out
}

// EditURL returns the edit-page link of the page (REQ-CNT-06). The page
// path maps to its Markdown file: "/start/" to "start.md".
func (c Config) EditURL(p Page) string {
	if c.EditBase == "" || p.Path == "" {
		return ""
	}
	base := c.EditBase
	if base[len(base)-1] != '/' {
		base += "/"
	}
	path := strings.TrimPrefix(p.Path, "/")
	path = strings.TrimSuffix(path, "/")
	if path == "" {
		path = "index"
	}
	return base + path + ".md"
}
ui/docs-shell/nav.go
// Package shell is the docs-shell registry block (REQ-CNT-06): a layout
// with a header, a sidebar, a table of contents, pagination and a 404, plus
// the client behaviours they need.
package shell

import "github.com/alternayte/gx"

// Nav is the sidebar model (REQ-CNT-06).
type Nav struct {
	Groups []NavGroup
}

// NavGroup is one sidebar section. A group with Collapsed starts closed.
type NavGroup struct {
	Label     string
	Badge     string
	Collapsed bool
	Items     []NavItem
}

// NavItem is one sidebar link. Items nest under a parent item.
type NavItem struct {
	Label string
	Href  gx.URL
	Badge string
	Items []NavItem
}

// Link is one header link: a social link or a plain link.
type Link struct {
	Label string
	Href  gx.URL
}

// Config is the site-wide shell data (REQ-CNT-06).
type Config struct {
	Title   string
	Version string
	Links   []Link
	// EditBase is the repository edit URL up to the file path, for example
	// https://github.com/acme/site/edit/main/content/docs/. Empty hides
	// the edit link.
	EditBase string
}

// Page is the shell data of one page (REQ-CNT-06).
type Page struct {
	Title string
	// Path is the site path of the page, for example /start/.
	Path string
	// Section is the sidebar group label of the page.
	Section string
	// Updated is the last-updated text. Empty hides it.
	Updated string
	// Prev and Next are the previous and next sidebar items.
	Prev *NavItem
	Next *NavItem
	// TOC holds the page headings, in document order.
	TOC []Heading
	// Depth limits the table of contents to headings at or below this
	// level. Zero means 3.
	Depth int
}

// Heading is one table-of-contents entry (REQ-CNT-06).
type Heading struct {
	Level int
	Text  string
	ID    string
}

The theme must define these tokens: --background, --foreground, --primary, --primary-foreground, --muted, --muted-foreground, --accent, --border.

Usage

The Gx docs shell: header, sidebar, table of contents, pagination, splash, 404 and the search dialog. It pairs with the docs kit.

var Site = shell.Config{Title: "Deedbox", Version: "v1.0.0", EditBase: "https://github.com/acme/site/edit/main/content/docs/"}

func View(e gx.Entry[DocMeta]) gx.Node {
    return shell.Shell(shell.ShellProps{Site: Site, Nav: Nav(), Page: PageFor(e), Children: DocsBody(e)})
}

Set [site] url in gx.toml so the export writes canonical links, sitemap.xml and robots.txt.

Examples

Header: Default

<shell.Header site={shell.Config{
  Title:    "Deedbox docs",
  Version:  "v0.1.0",
  Links:    []shell.Link{{Label: "GitHub", Href: gx.URL("https://example.com/gx")}},
  EditBase: "https://example.com/edit/main/content/docs/",
}} />

NotFound: Default

<shell.NotFound />

PageMeta: Default

<shell.PageMeta site={shell.Config{
  Title:    "Deedbox docs",
  Version:  "v0.1.0",
  Links:    []shell.Link{{Label: "GitHub", Href: gx.URL("https://example.com/gx")}},
  EditBase: "https://example.com/edit/main/content/docs/",
}} page={shell.Page{
  Title:   "Introduction",
  Path:    "/start/",
  Section: "Start",
  Updated: "Oct 2, 2026",
  Prev:    nil,
  Next:    &shell.NavItem{Label: "Routing", Href: gx.URL("/guides/routing/")},
  TOC: []shell.Heading{
    {Level: 2, Text: "Install", ID: "install"},
    {Level: 3, Text: "First page", ID: "first-page"},
  },
}} />

PageMeta: Plain

<shell.PageMeta site={shell.Config{Title: "Site"}} page={shell.Page{Title: "Page", Path: "/page/"}} />

Pagination: Both

<shell.Pagination prev={&shell.NavItem{Label: "Start", Href: gx.URL("/start/")}} next={&shell.NavItem{Label: "Routing", Href: gx.URL("/guides/routing/")}} />

Pagination: Next

<shell.Pagination next={&shell.NavItem{Label: "Routing", Href: gx.URL("/guides/routing/")}} prev={(*shell.NavItem)(nil)} />

Pagination: Empty

<shell.Pagination prev={(*shell.NavItem)(nil)} next={(*shell.NavItem)(nil)} />

SearchDialog: Default

<shell.SearchDialog />

Shell: Default

<shell.Shell site={shell.Config{
  Title:    "Deedbox docs",
  Version:  "v0.1.0",
  Links:    []shell.Link{{Label: "GitHub", Href: gx.URL("https://example.com/gx")}},
  EditBase: "https://example.com/edit/main/content/docs/",
}} nav={shell.Nav{Groups: []shell.NavGroup{
  shell.NavGroup{Label: "Start", Items: []shell.NavItem{
    {Label: "Introduction", Href: gx.URL("/start/"), Badge: "New"},
  }},
  shell.NavGroup{Label: "Guides", Badge: "12", Items: []shell.NavItem{shell.NavItem{Label: "Routing", Href: gx.URL("/guides/routing/"), Items: []shell.NavItem{
    {Label: "Pages", Href: gx.URL("/guides/routing/pages/")},
  }}}},
  {Label: "Errors", Collapsed: true, Items: []shell.NavItem{
    {Label: "GX1000", Href: gx.URL("/errors/GX1000/")},
  }},
}}} page={shell.Page{
  Title:   "Introduction",
  Path:    "/start/",
  Section: "Start",
  Updated: "Oct 2, 2026",
  Prev:    nil,
  Next:    &shell.NavItem{Label: "Routing", Href: gx.URL("/guides/routing/")},
  TOC: []shell.Heading{
    {Level: 2, Text: "Install", ID: "install"},
    {Level: 3, Text: "First page", ID: "first-page"},
  },
}}>
  Body.
</shell.Shell>
<shell.Sidebar nav={shell.Nav{Groups: []shell.NavGroup{
  shell.NavGroup{Label: "Start", Items: []shell.NavItem{
    {Label: "Introduction", Href: gx.URL("/start/"), Badge: "New"},
  }},
  shell.NavGroup{Label: "Guides", Badge: "12", Items: []shell.NavItem{shell.NavItem{Label: "Routing", Href: gx.URL("/guides/routing/"), Items: []shell.NavItem{
    {Label: "Pages", Href: gx.URL("/guides/routing/pages/")},
  }}}},
  {Label: "Errors", Collapsed: true, Items: []shell.NavItem{
    {Label: "GX1000", Href: gx.URL("/errors/GX1000/")},
  }},
}}} path="/guides/routing/pages/" />

SidebarGroup: Default

<shell.SidebarGroup group={shell.NavGroup{Label: "Start", Items: []shell.NavItem{
  {Label: "Introduction", Href: gx.URL("/start/"), Badge: "New"},
}}} path="/start/" />

SidebarGroup: Nested

<shell.SidebarGroup group={shell.NavGroup{Label: "Guides", Badge: "12", Items: []shell.NavItem{shell.NavItem{Label: "Routing", Href: gx.URL("/guides/routing/"), Items: []shell.NavItem{
  {Label: "Pages", Href: gx.URL("/guides/routing/pages/")},
}}}}} path="/guides/routing/pages/" />
<shell.SidebarItem item={shell.NavItem{Label: "Routing", Href: gx.URL("/guides/routing/")}} path="/guides/routing/" />

SidebarItem: Nested

<shell.SidebarItem item={shell.NavItem{Label: "Routing", Href: gx.URL("/guides/routing/"), Items: []shell.NavItem{
  {Label: "Pages", Href: gx.URL("/guides/routing/pages/")},
}}} path="/guides/routing/pages/" />

Splash: Default

<shell.Splash title="Deedbox" tagline="Event sourcing in .NET."><:actions>Docs</:actions></shell.Splash>

Splash: Plain

<shell.Splash title="Deedbox" />

ThemeSelect: Default

<shell.ThemeSelect />

Toc: Default

<shell.Toc headings={[]shell.Heading{
  {Level: 2, Text: "Install", ID: "install"},
  {Level: 3, Text: "First page", ID: "first-page"},
}} />

API reference

A tag sets a prop by its name with a lower-case first letter: Class is class.

shell.Header

Prop Type Default Description
Site Config Required Site is the site-wide data: the title, the version and the header links.

shell.NotFound

Prop Type Default Description
Title string "Page not found" Title is the heading of the page.
Message string "The page does not exist. Check the address or return to the start." Message is the text below the title.
Home string "/" Home is the target of the Go home link.

shell.PageMeta

Prop Type Default Description
Site Config Required Site is the site-wide data. Its EditBase builds the edit link; empty hides the link.
Page Page Required Page is the data of the current page: its path and its last-updated text.

shell.Pagination

Prop Type Default Description
Prev *NavItem Required Prev is the previous sidebar item. Nil omits the link.
Next *NavItem Required Next is the next sidebar item. Nil omits the link.

shell.SearchDialog

The component has no props.

shell.Shell

Prop Type Default Description
Site Config Required Site is the site-wide data: the title, the version and the header links.
Nav Nav Required Nav is the sidebar model: the groups and their items.
Page Page Required Page is the data of the current page: its path, its headings and its previous and next items.
Children gx.Node Required Children is the content of the page.

shell.Sidebar

Prop Type Default Description
Nav Nav Required Nav is the sidebar model: the groups and their items.
Path string Required Path is the site path of the current page. A nested item that holds the page starts open.

shell.SidebarGroup

Prop Type Default Description
Group NavGroup Required Group is the sidebar section: its label, its badge and its items. A group with Collapsed starts closed.
Path string Required Path is the site path of the current page. A nested item that holds the page starts open.

shell.SidebarItem

Prop Type Default Description
Item NavItem Required Item is the sidebar link. An item with nested items shows as a collapsible list.
Path string Required Path is the site path of the current page. A nested item that holds the page starts open.

shell.Splash

Prop Type Default Description
Title string Required Title is the heading of the splash.
Tagline string "" Tagline is the text below the title. Empty omits it.
Actions gx.Node nil Actions is the row of links or buttons below the tagline. Nil omits it.
Children gx.Node nil Children is the content below the actions. Nil omits it.

shell.ThemeSelect

The component has no props.

shell.Toc

Prop Type Default Description
Headings []Heading Required Headings holds the entries of the table of contents. Empty renders nothing.

Do and do not

Do

  • Sort the sidebar with explicit order values on the pages that need it.
  • Keep the page Path with a trailing slash.

Don't

  • Do not render Shell without a Page; the table of contents and the navigation need the path.
  • Do not put a Toc outside the shell.

Keyboard

Key Action
Enter, Space Opens the search dialog or the mobile menu.
Ctrl/Cmd K Opens the search dialog.
Escape Closes the search dialog.
Tab Moves through the header, the sidebar and the table of contents.