App Shell
An app page frame with a sidebar, a header and a main area.
<appshell.AppShell title="Acme">
<:nav>
<sidebar.SidebarGroup>
<sidebar.SidebarGroupLabel>Menu</sidebar.SidebarGroupLabel>
<sidebar.SidebarGroupContent>
<sidebar.SidebarMenu>
<sidebar.SidebarMenuItem>
<sidebar.SidebarMenuButton href={gx.URL("/")} active><span>Home</span></sidebar.SidebarMenuButton>
</sidebar.SidebarMenuItem>
<sidebar.SidebarMenuItem>
<sidebar.SidebarMenuButton href={gx.URL("/docs")}><span>Docs</span></sidebar.SidebarMenuButton>
</sidebar.SidebarMenuItem>
</sidebar.SidebarMenu>
</sidebar.SidebarGroupContent>
</sidebar.SidebarGroup>
</:nav>
<:footer><span class="px-2 text-xs text-sidebar-foreground/70">v0.1.0</span></:footer>
Page content.
</appshell.AppShell>Installation
Run the command in the app module.
gx add app-shellThe command also installs sidebar.
The command writes these files.
| File | Path in the app |
|---|---|
AppShell.gx |
ui/app-shell/AppShell.gx |
AppShell.fixtures.go |
ui/app-shell/AppShell.fixtures.go |
Install sidebar first.
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.
package appshell
import "github.com/alternayte/gx/registry/sidebar"
props {
// Title is the app name. It shows in the sidebar header and in the page header.
Title string
// Nav is the primary navigation. It renders in the sidebar content.
Nav gx.Node
// Footer is the content of the sidebar footer. A nil value renders no footer.
Footer gx.Node = nil
// Children is the content of the main area.
Children gx.Node
}
<div class="flex min-h-screen">
<sidebar.Sidebar id="gx-sidebar">
<sidebar.SidebarHeader>
<span class="px-2 py-1 text-sm font-semibold">{p.Title}</span>
</sidebar.SidebarHeader>
<sidebar.SidebarContent>
{p.Nav}
</sidebar.SidebarContent>
if p.Footer != nil {
<sidebar.SidebarFooter>{p.Footer}</sidebar.SidebarFooter>
}
</sidebar.Sidebar>
<div class="flex min-w-0 flex-1 flex-col">
<header class="flex h-14 items-center gap-2 border-b border-border px-4">
<sidebar.SidebarTrigger class="-ml-1 lg:hidden" />
<h1 class="text-sm font-semibold">{p.Title}</h1>
</header>
<main class="flex-1 p-6">{p.Children}</main>
</div>
</div>package appshell
import (
"github.com/alternayte/gx"
"github.com/alternayte/gx/registry/sidebar"
)
var AppShellFixtures = gx.Fixtures[AppShellProps]{
"Default": {
Title: "Acme",
Nav: sidebar.SidebarGroup(sidebar.SidebarGroupProps{Children: gx.Frag(
sidebar.SidebarGroupLabel(sidebar.SidebarGroupLabelProps{Children: gx.Text("Menu")}),
sidebar.SidebarGroupContent(sidebar.SidebarGroupContentProps{Children: sidebar.SidebarMenu(sidebar.SidebarMenuProps{Children: gx.Frag(
sidebar.SidebarMenuItem(sidebar.SidebarMenuItemProps{Children: sidebar.SidebarMenuButton(sidebar.SidebarMenuButtonProps{Href: gx.URL("/"), Active: true, Children: gx.El("span", nil, gx.Text("Home"))})}),
sidebar.SidebarMenuItem(sidebar.SidebarMenuItemProps{Children: sidebar.SidebarMenuButton(sidebar.SidebarMenuButtonProps{Href: gx.URL("/docs"), Children: gx.El("span", nil, gx.Text("Docs"))})}),
)})}),
)}),
Footer: gx.El("span", gx.Attrs{{Key: "class", Value: "px-2 text-xs text-sidebar-foreground/70"}}, gx.Text("v0.1.0")),
Children: gx.Text("Page content."),
},
}The theme must define these tokens: --border, --sidebar, --sidebar-foreground.
Usage
<appshell.AppShell title="Acme" nav={<appshell.Nav />}>
<p>Page content.</p>
</appshell.AppShell>The block is copied source. Wrap it in a gx.Layout so every page shares the frame.
Examples
Default
<appshell.AppShell title="Acme">
<:nav>
<sidebar.SidebarGroup>
<sidebar.SidebarGroupLabel>Menu</sidebar.SidebarGroupLabel>
<sidebar.SidebarGroupContent>
<sidebar.SidebarMenu>
<sidebar.SidebarMenuItem>
<sidebar.SidebarMenuButton href={gx.URL("/")} active><span>Home</span></sidebar.SidebarMenuButton>
</sidebar.SidebarMenuItem>
<sidebar.SidebarMenuItem>
<sidebar.SidebarMenuButton href={gx.URL("/docs")}><span>Docs</span></sidebar.SidebarMenuButton>
</sidebar.SidebarMenuItem>
</sidebar.SidebarMenu>
</sidebar.SidebarGroupContent>
</sidebar.SidebarGroup>
</:nav>
<:footer><span class="px-2 text-xs text-sidebar-foreground/70">v0.1.0</span></:footer>
Page content.
</appshell.AppShell>API reference
A tag sets a prop by its name with a lower-case first letter: Class is class.
appshell.AppShell
| Prop | Type | Default | Description |
|---|---|---|---|
Title |
string |
Required | Title is the app name. It shows in the sidebar header and in the page header. |
Nav |
gx.Node |
Required | Nav is the primary navigation. It renders in the sidebar content. |
Footer |
gx.Node |
nil |
Footer is the content of the sidebar footer. A nil value renders no footer. |
Children |
gx.Node |
Required | Children is the content of the main area. |
Do and do not
Do
- Keep one page title in the header.
- Put the primary navigation in
Nav.
Don't
- Do not add a second header inside the main area.
- Do not nest an app shell in another app shell.
Keyboard
| Key | Action |
|---|---|
| Tab | Moves to the menu button, then the sidebar links, then the page. |
| Enter, Space | Opens or closes the sidebar on a narrow screen. |