Skip to content
Gx
GitHub

Sidebar

A vertical panel for the app or docs navigation.

<sidebar.Sidebar id="demo-sidebar" class="h-72">
  <sidebar.SidebarHeader><span class="px-2 text-sm font-semibold">Gx</span></sidebar.SidebarHeader>
  <sidebar.SidebarContent>
    <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>
  </sidebar.SidebarContent>
  <sidebar.SidebarFooter><span class="px-2 text-xs text-sidebar-foreground/70">v0.1.0</span></sidebar.SidebarFooter>
</sidebar.Sidebar>

Installation

Run the command in the app module.

Terminal
gx add sidebar

The command also installs button, icons and input.

The command writes these files.

File Path in the app
Sidebar.gx ui/sidebar/Sidebar.gx
SidebarHeader.gx ui/sidebar/SidebarHeader.gx
SidebarContent.gx ui/sidebar/SidebarContent.gx
SidebarFooter.gx ui/sidebar/SidebarFooter.gx
SidebarInset.gx ui/sidebar/SidebarInset.gx
SidebarSeparator.gx ui/sidebar/SidebarSeparator.gx
SidebarInput.gx ui/sidebar/SidebarInput.gx
SidebarTrigger.gx ui/sidebar/SidebarTrigger.gx
SidebarGroup.gx ui/sidebar/SidebarGroup.gx
SidebarGroupLabel.gx ui/sidebar/SidebarGroupLabel.gx
SidebarGroupAction.gx ui/sidebar/SidebarGroupAction.gx
SidebarGroupContent.gx ui/sidebar/SidebarGroupContent.gx
SidebarMenu.gx ui/sidebar/SidebarMenu.gx
SidebarMenuItem.gx ui/sidebar/SidebarMenuItem.gx
SidebarMenuButton.gx ui/sidebar/SidebarMenuButton.gx
SidebarMenuAction.gx ui/sidebar/SidebarMenuAction.gx
SidebarMenuBadge.gx ui/sidebar/SidebarMenuBadge.gx
SidebarMenuSkeleton.gx ui/sidebar/SidebarMenuSkeleton.gx
SidebarMenuSub.gx ui/sidebar/SidebarMenuSub.gx
SidebarMenuSubItem.gx ui/sidebar/SidebarMenuSubItem.gx
SidebarMenuSubButton.gx ui/sidebar/SidebarMenuSubButton.gx
Sidebar.fixtures.go ui/sidebar/Sidebar.fixtures.go
SidebarHeader.fixtures.go ui/sidebar/SidebarHeader.fixtures.go
SidebarContent.fixtures.go ui/sidebar/SidebarContent.fixtures.go
SidebarFooter.fixtures.go ui/sidebar/SidebarFooter.fixtures.go
SidebarInset.fixtures.go ui/sidebar/SidebarInset.fixtures.go
SidebarSeparator.fixtures.go ui/sidebar/SidebarSeparator.fixtures.go
SidebarInput.fixtures.go ui/sidebar/SidebarInput.fixtures.go
SidebarTrigger.fixtures.go ui/sidebar/SidebarTrigger.fixtures.go
SidebarGroup.fixtures.go ui/sidebar/SidebarGroup.fixtures.go
SidebarGroupLabel.fixtures.go ui/sidebar/SidebarGroupLabel.fixtures.go
SidebarGroupAction.fixtures.go ui/sidebar/SidebarGroupAction.fixtures.go
SidebarGroupContent.fixtures.go ui/sidebar/SidebarGroupContent.fixtures.go
SidebarMenu.fixtures.go ui/sidebar/SidebarMenu.fixtures.go
SidebarMenuItem.fixtures.go ui/sidebar/SidebarMenuItem.fixtures.go
SidebarMenuButton.fixtures.go ui/sidebar/SidebarMenuButton.fixtures.go
SidebarMenuAction.fixtures.go ui/sidebar/SidebarMenuAction.fixtures.go
SidebarMenuBadge.fixtures.go ui/sidebar/SidebarMenuBadge.fixtures.go
SidebarMenuSkeleton.fixtures.go ui/sidebar/SidebarMenuSkeleton.fixtures.go
SidebarMenuSub.fixtures.go ui/sidebar/SidebarMenuSub.fixtures.go
SidebarMenuSubItem.fixtures.go ui/sidebar/SidebarMenuSubItem.fixtures.go
SidebarMenuSubButton.fixtures.go ui/sidebar/SidebarMenuSubButton.fixtures.go
styles.go ui/sidebar/styles.go

Install button, icons and input 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.

ui/sidebar/Sidebar.gx
package sidebar

props {
  // Id is the id of the root element.
  // SidebarTrigger shows and hides the sidebar with the id gx-sidebar.
  Id       string
  // Side sets the edge of the page that holds the sidebar: Left or Right.
  Side     Side = Left
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the sidebar: the header, content and footer.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<aside id={p.Id} data-gx-sidebar data-slot="sidebar" data-side={p.side()} class={gx.Cx("flex w-64 shrink-0 flex-col bg-sidebar text-sidebar-foreground data-[side=left]:border-r data-[side=right]:border-l", p.Class)} {...p.Attrs}>{p.Children}</aside>
ui/sidebar/SidebarHeader.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the header.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="sidebar-header" data-sidebar="header" class={gx.Cx("flex flex-col gap-2 p-2", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/sidebar/SidebarContent.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the scrolling content of the sidebar: the groups.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="sidebar-content" data-sidebar="content" class={gx.Cx("flex min-h-0 flex-1 flex-col gap-2 overflow-auto", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/sidebar/SidebarFooter.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the footer.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="sidebar-footer" data-sidebar="footer" class={gx.Cx("flex flex-col gap-2 p-2", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/sidebar/SidebarInset.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the main content of the page.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<main data-slot="sidebar-inset" class={gx.Cx("relative flex w-full flex-1 flex-col bg-background", p.Class)} {...p.Attrs}>{p.Children}</main>
ui/sidebar/SidebarSeparator.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class string = ""
  // Attrs adds HTML attributes to the root element.
  Attrs gx.Attrs = nil
}

<div data-slot="sidebar-separator" data-sidebar="separator" role="none" data-orientation="horizontal" class={gx.Cx("h-px w-full shrink-0 bg-border", "mx-2 w-auto bg-sidebar-border", p.Class)} {...p.Attrs}></div>
ui/sidebar/SidebarInput.gx
package sidebar

import "github.com/alternayte/gx/registry/input"

props {
  // Type is the type attribute of the input. An empty value is text.
  Type        string = "text"
  // Name is the form field name.
  Name        string = ""
  // Value is the value of the input.
  Value       string = ""
  // Placeholder is the text that shows while the input is empty.
  Placeholder string = ""
  // Class adds classes to the root element.
  Class       string = ""
  // Attrs adds HTML attributes to the root element.
  Attrs       gx.Attrs = nil
}

<input type={p.inputType()} name={p.Name} value={p.Value} placeholder={p.Placeholder} data-slot="sidebar-input" data-sidebar="input" class={gx.Cx(input.Class, "h-8 w-full bg-background shadow-none", p.Class)} {...p.Attrs} />
ui/sidebar/SidebarTrigger.gx
package sidebar

import "github.com/alternayte/gx/registry/button"
import "github.com/alternayte/gx/registry/icons"

props {
  // Controls is the id that aria-controls names. An empty value is gx-sidebar, the id the shell
  // runtime shows and hides.
  Controls string = "gx-sidebar"
  // Class adds classes to the root element.
  Class    string = ""
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<button.Button variant={button.Ghost} size={button.Icon} class={gx.Cx("size-7", p.Class)} attrs={p.attrs()}>
  <icons.PanelLeft />
  <span class="sr-only">Toggle Sidebar</span>
</button.Button>
ui/sidebar/SidebarGroup.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the group: the label, action and group content.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="sidebar-group" data-sidebar="group" class={gx.Cx("relative flex w-full min-w-0 flex-col p-2", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/sidebar/SidebarGroupLabel.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the label text of the group.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="sidebar-group-label" data-sidebar="group-label" class={gx.Cx("flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear motion-reduce:transition-none focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/sidebar/SidebarGroupAction.gx
package sidebar

props {
  // Label is the accessible name of the button.
  Label    string
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the icon of the button.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<button type="button" aria-label={p.Label} data-slot="sidebar-group-action" data-sidebar="group-action" class={gx.Cx("absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform motion-reduce:transition-none hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 after:absolute after:-inset-2 md:after:hidden", p.Class)} {...p.Attrs}>{p.Children}</button>
ui/sidebar/SidebarGroupContent.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the group, usually a SidebarMenu.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="sidebar-group-content" data-sidebar="group-content" class={gx.Cx("w-full text-sm", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/sidebar/SidebarMenu.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the menu items.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<ul data-slot="sidebar-menu" data-sidebar="menu" class={gx.Cx("flex w-full min-w-0 flex-col gap-1", p.Class)} {...p.Attrs}>{p.Children}</ul>
ui/sidebar/SidebarMenuItem.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the item: the menu button, action, badge and sub menu.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<li data-slot="sidebar-menu-item" data-sidebar="menu-item" class={gx.Cx("group/menu-item relative", p.Class)} {...p.Attrs}>{p.Children}</li>
ui/sidebar/SidebarMenuButton.gx
package sidebar

props {
  // Href is the URL of the link. An empty value renders a button in place of a link.
  Href     gx.URL = ""
  // Active marks the button of the current page.
  // A link gets aria-current and a button gets data-active.
  Active   bool = false
  // Variant sets the surface: Default or Outline.
  Variant  Variant = Default
  // Size sets the height and the text size: Md, Sm or Lg.
  Size     Size = Md
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the button: an icon and a span with the text.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

if p.Href != "" {
  <a href={p.Href} data-slot="sidebar-menu-button" data-sidebar="menu-button" data-size={p.size()} class={p.class()} {...p.attrs()}>{p.Children}</a>
} else {
  <button type="button" data-slot="sidebar-menu-button" data-sidebar="menu-button" data-size={p.size()} class={p.class()} {...p.buttonAttrs()}>{p.Children}</button>
}
ui/sidebar/SidebarMenuAction.gx
package sidebar

props {
  // Label is the accessible name of the button.
  Label       string
  // ShowOnHover hides the action on a wide screen until its item has the pointer or the focus.
  ShowOnHover bool = false
  // Class adds classes to the root element.
  Class       string = ""
  // Children is the icon of the button.
  Children    gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs       gx.Attrs = nil
}

<button type="button" aria-label={p.Label} data-slot="sidebar-menu-action" data-sidebar="menu-action" class={gx.Cx("absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform motion-reduce:transition-none peer-hover/menu-button:text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 after:absolute after:-inset-2 md:after:hidden peer-data-[size=sm]/menu-button:top-1 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5", hoverClass[p.ShowOnHover], p.Class)} {...p.Attrs}>{p.Children}</button>
ui/sidebar/SidebarMenuBadge.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the badge text.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="sidebar-menu-badge" data-sidebar="menu-badge" class={gx.Cx("pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none peer-hover/menu-button:text-sidebar-accent-foreground peer-aria-[current=page]/menu-button:text-sidebar-accent-foreground peer-data-active/menu-button:text-sidebar-accent-foreground peer-data-[size=sm]/menu-button:top-1 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/sidebar/SidebarMenuSkeleton.gx
package sidebar

props {
  // ShowIcon adds an icon placeholder before the text bar.
  ShowIcon bool = false
  // Width is the maximum width of the text bar as a CSS length. An empty value is 70%.
  Width    string = "70%"
  // Class adds classes to the root element.
  Class    string = ""
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div data-slot="sidebar-menu-skeleton" data-sidebar="menu-skeleton" class={gx.Cx("flex h-8 items-center gap-2 rounded-md px-2", p.Class)} {...p.Attrs}>
  if p.ShowIcon {
    <div data-slot="skeleton" data-sidebar="menu-skeleton-icon" class="size-4 animate-pulse rounded-md bg-accent"></div>
  }
  <div data-slot="skeleton" data-sidebar="menu-skeleton-text" style={gx.Style("--skeleton-width: " + p.width())} class="h-4 max-w-(--skeleton-width) flex-1 animate-pulse rounded-md bg-accent"></div>
</div>
ui/sidebar/SidebarMenuSub.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the sub menu items.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<ul data-slot="sidebar-menu-sub" data-sidebar="menu-sub" class={gx.Cx("mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5", p.Class)} {...p.Attrs}>{p.Children}</ul>
ui/sidebar/SidebarMenuSubItem.gx
package sidebar

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the sub item: a SidebarMenuSubButton.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<li data-slot="sidebar-menu-sub-item" data-sidebar="menu-sub-item" class={gx.Cx("group/menu-sub-item relative", p.Class)} {...p.Attrs}>{p.Children}</li>
ui/sidebar/SidebarMenuSubButton.gx
package sidebar

props {
  // Href is the URL of the link.
  Href     gx.URL
  // Active marks the link of the current page. It sets aria-current and the active style.
  Active   bool = false
  // Size sets the text size: Md or Sm.
  Size     Size = Md
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the link: an icon and a span with the text.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<a href={p.Href} data-slot="sidebar-menu-sub-button" data-sidebar="menu-sub-button" data-size={p.size()} class={gx.Cx("flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground aria-[current=page]:bg-sidebar-accent aria-[current=page]:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground", subSizeClass[Size(p.size())], subActiveClass[p.Active], p.Class)} {...p.attrs()}>{p.Children}</a>
ui/sidebar/Sidebar.fixtures.go
package sidebar

import (
	"github.com/alternayte/gx"
	"github.com/alternayte/gx/registry/icons"
)

// more is the icon of the action fixtures.
func more() gx.Node {
	return icons.Ellipsis(icons.EllipsisProps{})
}

// demoMenu is the menu of the sidebar fixtures.
func demoMenu() gx.Node {
	return SidebarMenu(SidebarMenuProps{Children: gx.Frag(
		SidebarMenuItem(SidebarMenuItemProps{Children: SidebarMenuButton(SidebarMenuButtonProps{Href: gx.URL("/"), Active: true, Children: gx.El("span", nil, gx.Text("Home"))})}),
		SidebarMenuItem(SidebarMenuItemProps{Children: SidebarMenuButton(SidebarMenuButtonProps{Href: gx.URL("/docs"), Children: gx.El("span", nil, gx.Text("Docs"))})}),
	)})
}

var SidebarFixtures = gx.Fixtures[SidebarProps]{
	"Full": {Id: "demo-sidebar", Class: "h-72", Children: gx.Frag(
		SidebarHeader(SidebarHeaderProps{Children: gx.El("span", gx.Attrs{{Key: "class", Value: "px-2 text-sm font-semibold"}}, gx.Text("Gx"))}),
		SidebarContent(SidebarContentProps{Children: SidebarGroup(SidebarGroupProps{Children: gx.Frag(
			SidebarGroupLabel(SidebarGroupLabelProps{Children: gx.Text("Menu")}),
			SidebarGroupContent(SidebarGroupContentProps{Children: demoMenu()}),
		)})}),
		SidebarFooter(SidebarFooterProps{Children: gx.El("span", gx.Attrs{{Key: "class", Value: "px-2 text-xs text-sidebar-foreground/70"}}, gx.Text("v0.1.0"))}),
	)},
	"Right": {Id: "demo-sidebar-right", Side: Right, Class: "h-40", Children: SidebarContent(SidebarContentProps{
		Children: SidebarGroup(SidebarGroupProps{Children: demoMenu()}),
	})},
	"Hidden": {Id: "demo-sidebar-hidden", Attrs: gx.Attrs{gx.Bool("hidden", true)}},
}
ui/sidebar/SidebarHeader.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarHeaderFixtures = gx.Fixtures[SidebarHeaderProps]{"Empty": {}}
ui/sidebar/SidebarContent.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarContentFixtures = gx.Fixtures[SidebarContentProps]{"Empty": {}}
ui/sidebar/SidebarFooter.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarFooterFixtures = gx.Fixtures[SidebarFooterProps]{"Empty": {}}
ui/sidebar/SidebarInset.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarInsetFixtures = gx.Fixtures[SidebarInsetProps]{"Page": {Class: "p-4", Children: gx.Text("Page content.")}}
ui/sidebar/SidebarSeparator.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarSeparatorFixtures = gx.Fixtures[SidebarSeparatorProps]{"Default": {}}
ui/sidebar/SidebarInput.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarInputFixtures = gx.Fixtures[SidebarInputProps]{"Search": {Placeholder: "Search the docs", Attrs: gx.Attrs{{Key: "aria-label", Value: "Search"}}}}
ui/sidebar/SidebarTrigger.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarTriggerFixtures = gx.Fixtures[SidebarTriggerProps]{"Default": {Controls: "demo-sidebar"}}
ui/sidebar/SidebarGroup.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarGroupFixtures = gx.Fixtures[SidebarGroupProps]{
	"Labelled": {Class: "w-64 bg-sidebar text-sidebar-foreground", Children: gx.Frag(
		SidebarGroupLabel(SidebarGroupLabelProps{Children: gx.Text("Projects")}),
		SidebarGroupAction(SidebarGroupActionProps{Label: "Project options", Children: more()}),
		SidebarGroupContent(SidebarGroupContentProps{Children: demoMenu()}),
	)},
}
ui/sidebar/SidebarGroupLabel.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarGroupLabelFixtures = gx.Fixtures[SidebarGroupLabelProps]{"Label": {Children: gx.Text("Projects")}}
ui/sidebar/SidebarGroupAction.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarGroupActionFixtures = gx.Fixtures[SidebarGroupActionProps]{"Add": {Label: "Project options", Children: more()}}

// SidebarGroupActionWrap renders the action in a group, which positions
// it.
func SidebarGroupActionWrap(n gx.Node) gx.Node {
	return SidebarGroup(SidebarGroupProps{Class: "w-64", Children: gx.Frag(
		SidebarGroupLabel(SidebarGroupLabelProps{Children: gx.Text("Projects")}),
		n,
	)})
}
ui/sidebar/SidebarGroupContent.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarGroupContentFixtures = gx.Fixtures[SidebarGroupContentProps]{"Empty": {}}
ui/sidebar/SidebarMenu.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarMenuFixtures = gx.Fixtures[SidebarMenuProps]{
	"Nested": {Class: "w-64", Children: gx.Frag(
		SidebarMenuItem(SidebarMenuItemProps{Children: gx.Frag(
			SidebarMenuButton(SidebarMenuButtonProps{Href: gx.URL("/docs"), Children: gx.El("span", nil, gx.Text("Docs"))}),
			SidebarMenuBadge(SidebarMenuBadgeProps{Children: gx.Text("12")}),
			SidebarMenuSub(SidebarMenuSubProps{Children: gx.Frag(
				SidebarMenuSubItem(SidebarMenuSubItemProps{Children: SidebarMenuSubButton(SidebarMenuSubButtonProps{Href: gx.URL("/docs/start"), Active: true, Children: gx.El("span", nil, gx.Text("Get started"))})}),
				SidebarMenuSubItem(SidebarMenuSubItemProps{Children: SidebarMenuSubButton(SidebarMenuSubButtonProps{Href: gx.URL("/docs/forms"), Children: gx.El("span", nil, gx.Text("Forms"))})}),
			)}),
		)}),
		SidebarMenuItem(SidebarMenuItemProps{Children: gx.Frag(
			SidebarMenuButton(SidebarMenuButtonProps{Href: gx.URL("/projects"), Children: gx.El("span", nil, gx.Text("Projects"))}),
			SidebarMenuAction(SidebarMenuActionProps{Label: "Project options", Children: more()}),
		)}),
	)},
	"Loading": {Class: "w-64", Children: gx.Frag(
		SidebarMenuItem(SidebarMenuItemProps{Children: SidebarMenuSkeleton(SidebarMenuSkeletonProps{ShowIcon: true, Width: "80%"})}),
		SidebarMenuItem(SidebarMenuItemProps{Children: SidebarMenuSkeleton(SidebarMenuSkeletonProps{ShowIcon: true, Width: "55%"})}),
		SidebarMenuItem(SidebarMenuItemProps{Children: SidebarMenuSkeleton(SidebarMenuSkeletonProps{ShowIcon: true, Width: "70%"})}),
	)},
}
ui/sidebar/SidebarMenuItem.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarMenuItemFixtures = gx.Fixtures[SidebarMenuItemProps]{"Empty": {}}

// SidebarMenuItemWrap renders the part in a menu, where it belongs.
func SidebarMenuItemWrap(n gx.Node) gx.Node {
	return SidebarMenu(SidebarMenuProps{Class: "w-64", Children: n})
}
ui/sidebar/SidebarMenuButton.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarMenuButtonFixtures = gx.Fixtures[SidebarMenuButtonProps]{
	"Link":    {Href: gx.URL("/docs"), Children: gx.El("span", nil, gx.Text("Docs"))},
	"Active":  {Href: gx.URL("/"), Active: true, Children: gx.El("span", nil, gx.Text("Home"))},
	"Button":  {Children: gx.El("span", nil, gx.Text("Sign out"))},
	"Outline": {Variant: Outline, Href: gx.URL("/docs"), Children: gx.El("span", nil, gx.Text("Docs"))},
	"Small":   {Size: Sm, Href: gx.URL("/docs"), Children: gx.El("span", nil, gx.Text("Docs"))},
	"Large":   {Size: Lg, Href: gx.URL("/docs"), Children: gx.El("span", nil, gx.Text("Docs"))},
}

// SidebarMenuButtonWrap renders the part in a menu, where it belongs.
func SidebarMenuButtonWrap(n gx.Node) gx.Node {
	return SidebarMenu(SidebarMenuProps{Class: "w-64", Children: SidebarMenuItem(SidebarMenuItemProps{Children: n})})
}
ui/sidebar/SidebarMenuAction.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarMenuActionFixtures = gx.Fixtures[SidebarMenuActionProps]{
	"Add":   {Label: "Project options", Children: more()},
	"Hover": {Label: "Project options", ShowOnHover: true, Children: more()},
}

// SidebarMenuActionWrap renders the part in a menu, where it belongs.
func SidebarMenuActionWrap(n gx.Node) gx.Node {
	return SidebarMenu(SidebarMenuProps{Class: "w-64", Children: SidebarMenuItem(SidebarMenuItemProps{Children: gx.Frag(SidebarMenuButton(SidebarMenuButtonProps{Href: gx.URL("/projects"), Children: gx.El("span", nil, gx.Text("Projects"))}), n)})})
}
ui/sidebar/SidebarMenuBadge.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarMenuBadgeFixtures = gx.Fixtures[SidebarMenuBadgeProps]{"Count": {Children: gx.Text("12")}}

// SidebarMenuBadgeWrap renders the part in a menu, where it belongs.
func SidebarMenuBadgeWrap(n gx.Node) gx.Node {
	return SidebarMenu(SidebarMenuProps{Class: "w-64", Children: SidebarMenuItem(SidebarMenuItemProps{Children: gx.Frag(SidebarMenuButton(SidebarMenuButtonProps{Href: gx.URL("/projects"), Children: gx.El("span", nil, gx.Text("Projects"))}), n)})})
}
ui/sidebar/SidebarMenuSkeleton.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarMenuSkeletonFixtures = gx.Fixtures[SidebarMenuSkeletonProps]{
	"Text": {},
	"Icon": {ShowIcon: true, Width: "55%"},
}

// SidebarMenuSkeletonWrap renders the part in a menu, where it belongs.
func SidebarMenuSkeletonWrap(n gx.Node) gx.Node {
	return SidebarMenu(SidebarMenuProps{Class: "w-64", Children: SidebarMenuItem(SidebarMenuItemProps{Children: n})})
}
ui/sidebar/SidebarMenuSub.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarMenuSubFixtures = gx.Fixtures[SidebarMenuSubProps]{
	"Two": {Children: gx.Frag(
		SidebarMenuSubItem(SidebarMenuSubItemProps{Children: SidebarMenuSubButton(SidebarMenuSubButtonProps{Href: gx.URL("/docs/start"), Active: true, Children: gx.El("span", nil, gx.Text("Get started"))})}),
		SidebarMenuSubItem(SidebarMenuSubItemProps{Children: SidebarMenuSubButton(SidebarMenuSubButtonProps{Href: gx.URL("/docs/forms"), Size: Sm, Children: gx.El("span", nil, gx.Text("Forms"))})}),
	)},
}

// SidebarMenuSubWrap renders the part in a menu, where it belongs.
func SidebarMenuSubWrap(n gx.Node) gx.Node {
	return SidebarMenu(SidebarMenuProps{Class: "w-64", Children: SidebarMenuItem(SidebarMenuItemProps{Children: n})})
}
ui/sidebar/SidebarMenuSubItem.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarMenuSubItemFixtures = gx.Fixtures[SidebarMenuSubItemProps]{"Empty": {}}

// SidebarMenuSubItemWrap renders the part in a sub menu, where it belongs.
func SidebarMenuSubItemWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "class", Value: "w-64"}}, SidebarMenuSub(SidebarMenuSubProps{Children: n}))
}
ui/sidebar/SidebarMenuSubButton.fixtures.go
package sidebar

import "github.com/alternayte/gx"

var SidebarMenuSubButtonFixtures = gx.Fixtures[SidebarMenuSubButtonProps]{
	"Link":   {Href: gx.URL("/docs/forms"), Children: gx.El("span", nil, gx.Text("Forms"))},
	"Active": {Href: gx.URL("/docs/start"), Active: true, Children: gx.El("span", nil, gx.Text("Get started"))},
	"Small":  {Href: gx.URL("/docs/forms"), Size: Sm, Children: gx.El("span", nil, gx.Text("Forms"))},
}

// SidebarMenuSubButtonWrap renders the part in a sub menu, where it belongs.
func SidebarMenuSubButtonWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "class", Value: "w-64"}}, SidebarMenuSub(SidebarMenuSubProps{Children: SidebarMenuSubItem(SidebarMenuSubItemProps{Children: n})}))
}
ui/sidebar/styles.go
package sidebar

import "github.com/alternayte/gx"

// Side is the edge of the page that holds the sidebar.
type Side string

// The sides of sidebar.Sidebar.
const (
	Left  Side = "left"
	Right Side = "right"
)

// side returns the data-side value; a zero value is Left. The border of
// the sidebar follows this attribute.
func (p SidebarProps) side() string {
	if p.Side == "" {
		return string(Left)
	}
	return string(p.Side)
}

// Variant is the surface of a menu button.
type Variant string

// The variants of sidebar.SidebarMenuButton.
const (
	Default Variant = "default"
	Outline Variant = "outline"
)

var variantClass = gx.Enum[Variant]{
	Default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
	Outline: "bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
}

// Size is the height and the text size of a menu button.
type Size string

// The sizes of sidebar.SidebarMenuButton. A sub button has Md and Sm.
const (
	Md Size = "default"
	Sm Size = "sm"
	Lg Size = "lg"
)

var sizeClass = gx.Enum[Size]{
	Md: "h-8 text-sm",
	Sm: "h-7 text-xs",
	Lg: "h-12 text-sm",
}

// subSizeClass is the text size of a sub button. Its height is fixed.
var subSizeClass = gx.Enum[Size]{
	Md: "text-sm",
	Sm: "text-xs",
	Lg: "text-sm",
}

// hoverClass hides a menu action on a wide screen until its item has the
// pointer or the focus.
var hoverClass = map[bool]string{
	true:  "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-aria-[current=page]/menu-button:text-sidebar-accent-foreground peer-data-active/menu-button:text-sidebar-accent-foreground data-[state=open]:opacity-100 md:opacity-0",
	false: "",
}

// size returns the data-size value; a zero value is Md. The action and the
// badge of the item read this attribute.
func (p SidebarMenuButtonProps) size() string {
	if p.Size == "" {
		return string(Md)
	}
	return string(p.Size)
}

// class returns the classes of one menu button.
func (p SidebarMenuButtonProps) class() string {
	const base = "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] motion-reduce:transition-none group-has-data-[sidebar=menu-action]/menu-item:pr-8 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-[current=page]:bg-sidebar-accent aria-[current=page]:font-medium aria-[current=page]:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0"
	variant := p.Variant
	if variant == "" {
		variant = Default
	}
	return gx.Cx(base, variantClass[variant], sizeClass[Size(p.size())], activeClass[p.Active], p.Class)
}

// activeClass is the look of a menu button with Active set. A link also
// takes this look from aria-current and data-active, which the framework
// keeps current for a typed href.
var activeClass = map[bool]string{
	true:  "bg-sidebar-accent font-medium text-sidebar-accent-foreground",
	false: "",
}

// subActiveClass is the look of a sub button with Active set.
var subActiveClass = map[bool]string{
	true:  "bg-sidebar-accent text-sidebar-accent-foreground",
	false: "",
}

// attrs marks the link of the current page, then adds the caller's
// attributes.
func (p SidebarMenuButtonProps) attrs() gx.Attrs {
	if !p.Active {
		return p.Attrs
	}
	return append(gx.Attrs{{Key: "aria-current", Value: "page"}}, p.Attrs...)
}

// buttonAttrs marks an active button, then adds the caller's attributes.
// The action and the badge of the item read data-active.
func (p SidebarMenuButtonProps) buttonAttrs() gx.Attrs {
	if !p.Active {
		return p.Attrs
	}
	return append(gx.Attrs{{Key: "data-active", Value: "true"}}, p.Attrs...)
}

// size returns the data-size value; a zero value is Md.
func (p SidebarMenuSubButtonProps) size() string {
	if p.Size == "" {
		return string(Md)
	}
	return string(p.Size)
}

// attrs marks the link of the current page, then adds the caller's
// attributes.
func (p SidebarMenuSubButtonProps) attrs() gx.Attrs {
	if !p.Active {
		return p.Attrs
	}
	return append(gx.Attrs{{Key: "aria-current", Value: "page"}}, p.Attrs...)
}

// width returns the width of the text bar; a zero value is 70%.
func (p SidebarMenuSkeletonProps) width() string {
	if p.Width == "" {
		return "70%"
	}
	return p.Width
}

// inputType returns the type of the input; a zero value is text.
func (p SidebarInputProps) inputType() string {
	if p.Type == "" {
		return "text"
	}
	return p.Type
}

// controls returns the id of the sidebar; a zero value is gx-sidebar, the
// id the shell runtime shows and hides.
func (p SidebarTriggerProps) controls() string {
	if p.Controls == "" {
		return "gx-sidebar"
	}
	return p.Controls
}

// attrs returns the attributes of the trigger: the shell runtime reads
// data-gx-menu and keeps aria-expanded current.
func (p SidebarTriggerProps) attrs() gx.Attrs {
	return append(gx.Attrs{
		{Key: "data-sidebar", Value: "trigger"},
		gx.Bool("data-gx-menu", true),
		{Key: "aria-controls", Value: p.controls()},
		{Key: "aria-expanded", Value: "false"},
	}, p.Attrs...)
}

The theme must define these tokens: --border, --background, --accent, --sidebar, --sidebar-foreground, --sidebar-accent, --sidebar-accent-foreground, --sidebar-border, --sidebar-ring.

Usage

<sidebar.Sidebar id="gx-sidebar">
  <sidebar.SidebarHeader>
    <sidebar.SidebarInput name="q" placeholder="Search" />
  </sidebar.SidebarHeader>
  <sidebar.SidebarContent>
    <sidebar.SidebarGroup>
      <sidebar.SidebarGroupLabel>Menu</sidebar.SidebarGroupLabel>
      <sidebar.SidebarGroupContent>
        <sidebar.SidebarMenu>
          <sidebar.SidebarMenuItem>
            <sidebar.SidebarMenuButton href={homeRoute} active={true}><span>Home</span></sidebar.SidebarMenuButton>
          </sidebar.SidebarMenuItem>
          <sidebar.SidebarMenuItem>
            <sidebar.SidebarMenuButton href={docsRoute}><span>Docs</span></sidebar.SidebarMenuButton>
            <sidebar.SidebarMenuBadge>12</sidebar.SidebarMenuBadge>
            <sidebar.SidebarMenuSub>
              <sidebar.SidebarMenuSubItem>
                <sidebar.SidebarMenuSubButton href={formsRoute}><span>Forms</span></sidebar.SidebarMenuSubButton>
              </sidebar.SidebarMenuSubItem>
            </sidebar.SidebarMenuSub>
          </sidebar.SidebarMenuItem>
        </sidebar.SidebarMenu>
      </sidebar.SidebarGroupContent>
    </sidebar.SidebarGroup>
  </sidebar.SidebarContent>
  <sidebar.SidebarFooter>v0.1.0</sidebar.SidebarFooter>
</sidebar.Sidebar>
<sidebar.SidebarInset>
  <sidebar.SidebarTrigger class="lg:hidden" />
</sidebar.SidebarInset>

SidebarMenuButton is a link when it has Href, and a button when it has none. Its variants are Default and Outline. Its sizes are Md, Sm and Lg.

SidebarMenuAction and SidebarMenuBadge sit at the right of the button of their item. SidebarGroupAction sits at the right of the group label. SidebarMenuSkeleton is the placeholder of one menu row.

SidebarTrigger shows and hides the sidebar with the id gx-sidebar on a narrow screen. The sidebar is always visible on a wide screen. Set side={sidebar.Right} for a sidebar at the right edge.

The sidebar is static. It has no collapsed icon mode, no rail, no floating or inset variant and no mobile sheet.

Examples

<sidebar.Sidebar id="demo-sidebar" class="h-72">
  <sidebar.SidebarHeader><span class="px-2 text-sm font-semibold">Gx</span></sidebar.SidebarHeader>
  <sidebar.SidebarContent>
    <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>
  </sidebar.SidebarContent>
  <sidebar.SidebarFooter><span class="px-2 text-xs text-sidebar-foreground/70">v0.1.0</span></sidebar.SidebarFooter>
</sidebar.Sidebar>
<sidebar.Sidebar id="demo-sidebar-right" side={sidebar.Right} class="h-40">
  <sidebar.SidebarContent>
    <sidebar.SidebarGroup>
      <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.SidebarGroup>
  </sidebar.SidebarContent>
</sidebar.Sidebar>
<sidebar.Sidebar id="demo-sidebar-hidden" attrs={gx.Attrs{gx.Bool("hidden", true)}} children={gx.Frag()} />

SidebarContent: Empty

<sidebar.SidebarContent children={gx.Frag()} />

SidebarFooter: Empty

<sidebar.SidebarFooter children={gx.Frag()} />

SidebarGroup: Labelled

<sidebar.SidebarGroup class="w-64 bg-sidebar text-sidebar-foreground">
  <sidebar.SidebarGroupLabel>Projects</sidebar.SidebarGroupLabel>
  <sidebar.SidebarGroupAction label="Project options"><icons.Ellipsis /></sidebar.SidebarGroupAction>
  <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>

SidebarGroupAction: Add

<sidebar.SidebarGroupAction label="Project options"><icons.Ellipsis /></sidebar.SidebarGroupAction>

SidebarGroupContent: Empty

<sidebar.SidebarGroupContent children={gx.Frag()} />

SidebarGroupLabel: Label

<sidebar.SidebarGroupLabel>Projects</sidebar.SidebarGroupLabel>

SidebarHeader: Empty

<sidebar.SidebarHeader children={gx.Frag()} />
<sidebar.SidebarInput placeholder="Search the docs" attrs={gx.Attrs{{Key: "aria-label", Value: "Search"}}} />

SidebarInset: Page

<sidebar.SidebarInset class="p-4">Page content.</sidebar.SidebarInset>

SidebarMenu: Nested

<sidebar.SidebarMenu class="w-64">
  <sidebar.SidebarMenuItem>
    <sidebar.SidebarMenuButton href={gx.URL("/docs")}><span>Docs</span></sidebar.SidebarMenuButton>
    <sidebar.SidebarMenuBadge>12</sidebar.SidebarMenuBadge>
    <sidebar.SidebarMenuSub>
      <sidebar.SidebarMenuSubItem>
        <sidebar.SidebarMenuSubButton href={gx.URL("/docs/start")} active>
          <span>Get started</span>
        </sidebar.SidebarMenuSubButton>
      </sidebar.SidebarMenuSubItem>
      <sidebar.SidebarMenuSubItem>
        <sidebar.SidebarMenuSubButton href={gx.URL("/docs/forms")}><span>Forms</span></sidebar.SidebarMenuSubButton>
      </sidebar.SidebarMenuSubItem>
    </sidebar.SidebarMenuSub>
  </sidebar.SidebarMenuItem>
  <sidebar.SidebarMenuItem>
    <sidebar.SidebarMenuButton href={gx.URL("/projects")}><span>Projects</span></sidebar.SidebarMenuButton>
    <sidebar.SidebarMenuAction label="Project options"><icons.Ellipsis /></sidebar.SidebarMenuAction>
  </sidebar.SidebarMenuItem>
</sidebar.SidebarMenu>

SidebarMenu: Loading

<sidebar.SidebarMenu class="w-64">
  <sidebar.SidebarMenuItem><sidebar.SidebarMenuSkeleton showIcon width="80%" /></sidebar.SidebarMenuItem>
  <sidebar.SidebarMenuItem><sidebar.SidebarMenuSkeleton showIcon width="55%" /></sidebar.SidebarMenuItem>
  <sidebar.SidebarMenuItem><sidebar.SidebarMenuSkeleton showIcon width="70%" /></sidebar.SidebarMenuItem>
</sidebar.SidebarMenu>

SidebarMenuAction: Add

<sidebar.SidebarMenuAction label="Project options"><icons.Ellipsis /></sidebar.SidebarMenuAction>

SidebarMenuAction: Hover

<sidebar.SidebarMenuAction label="Project options" showOnHover><icons.Ellipsis /></sidebar.SidebarMenuAction>

SidebarMenuBadge: Count

<sidebar.SidebarMenuBadge>12</sidebar.SidebarMenuBadge>
<sidebar.SidebarMenuButton href={gx.URL("/docs")}><span>Docs</span></sidebar.SidebarMenuButton>

SidebarMenuButton: Active

<sidebar.SidebarMenuButton href={gx.URL("/")} active><span>Home</span></sidebar.SidebarMenuButton>

SidebarMenuButton: Button

<sidebar.SidebarMenuButton><span>Sign out</span></sidebar.SidebarMenuButton>

SidebarMenuButton: Outline

<sidebar.SidebarMenuButton variant={sidebar.Outline} href={gx.URL("/docs")}>
  <span>Docs</span>
</sidebar.SidebarMenuButton>

SidebarMenuButton: Small

<sidebar.SidebarMenuButton size={sidebar.Sm} href={gx.URL("/docs")}><span>Docs</span></sidebar.SidebarMenuButton>

SidebarMenuButton: Large

<sidebar.SidebarMenuButton size={sidebar.Lg} href={gx.URL("/docs")}><span>Docs</span></sidebar.SidebarMenuButton>

SidebarMenuItem: Empty

<sidebar.SidebarMenuItem children={gx.Frag()} />

SidebarMenuSkeleton: Text

<sidebar.SidebarMenuSkeleton />

SidebarMenuSkeleton: Icon

<sidebar.SidebarMenuSkeleton showIcon width="55%" />

SidebarMenuSub: Two

<sidebar.SidebarMenuSub>
  <sidebar.SidebarMenuSubItem>
    <sidebar.SidebarMenuSubButton href={gx.URL("/docs/start")} active>
      <span>Get started</span>
    </sidebar.SidebarMenuSubButton>
  </sidebar.SidebarMenuSubItem>
  <sidebar.SidebarMenuSubItem>
    <sidebar.SidebarMenuSubButton href={gx.URL("/docs/forms")} size={sidebar.Sm}>
      <span>Forms</span>
    </sidebar.SidebarMenuSubButton>
  </sidebar.SidebarMenuSubItem>
</sidebar.SidebarMenuSub>
<sidebar.SidebarMenuSubButton href={gx.URL("/docs/forms")}><span>Forms</span></sidebar.SidebarMenuSubButton>

SidebarMenuSubButton: Active

<sidebar.SidebarMenuSubButton href={gx.URL("/docs/start")} active>
  <span>Get started</span>
</sidebar.SidebarMenuSubButton>

SidebarMenuSubButton: Small

<sidebar.SidebarMenuSubButton href={gx.URL("/docs/forms")} size={sidebar.Sm}>
  <span>Forms</span>
</sidebar.SidebarMenuSubButton>

SidebarMenuSubItem: Empty

<sidebar.SidebarMenuSubItem children={gx.Frag()} />

SidebarSeparator: Default

<sidebar.SidebarSeparator />

SidebarTrigger: Default

<sidebar.SidebarTrigger controls="demo-sidebar" />

API reference

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

sidebar.Sidebar

Prop Type Default Description
Id string Required Id is the id of the root element. SidebarTrigger shows and hides the sidebar with the id gx-sidebar.
Side Side Left Side sets the edge of the page that holds the sidebar: Left or Right.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the sidebar: the header, content and footer.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarContent

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the scrolling content of the sidebar: the groups.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarFooter

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the footer.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarGroup

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the group: the label, action and group content.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarGroupAction

Prop Type Default Description
Label string Required Label is the accessible name of the button.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the icon of the button.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarGroupContent

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the group, usually a SidebarMenu.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarGroupLabel

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the label text of the group.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarHeader

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the header.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarInput

Prop Type Default Description
Type string "text" Type is the type attribute of the input. An empty value is text.
Name string "" Name is the form field name.
Value string "" Value is the value of the input.
Placeholder string "" Placeholder is the text that shows while the input is empty.
Class string "" Class adds classes to the root element.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarInset

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the main content of the page.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarMenu

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the menu items.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarMenuAction

Prop Type Default Description
Label string Required Label is the accessible name of the button.
ShowOnHover bool false ShowOnHover hides the action on a wide screen until its item has the pointer or the focus.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the icon of the button.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarMenuBadge

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the badge text.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarMenuButton

Prop Type Default Description
Href gx.URL "" Href is the URL of the link. An empty value renders a button in place of a link.
Active bool false Active marks the button of the current page. A link gets aria-current and a button gets data-active.
Variant Variant Default Variant sets the surface: Default or Outline.
Size Size Md Size sets the height and the text size: Md, Sm or Lg.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the button: an icon and a span with the text.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarMenuItem

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the item: the menu button, action, badge and sub menu.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarMenuSkeleton

Prop Type Default Description
ShowIcon bool false ShowIcon adds an icon placeholder before the text bar.
Width string "70%" Width is the maximum width of the text bar as a CSS length. An empty value is 70%.
Class string "" Class adds classes to the root element.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarMenuSub

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the sub menu items.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarMenuSubButton

Prop Type Default Description
Href gx.URL Required Href is the URL of the link.
Active bool false Active marks the link of the current page. It sets aria-current and the active style.
Size Size Md Size sets the text size: Md or Sm.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the link: an icon and a span with the text.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarMenuSubItem

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the sub item: a SidebarMenuSubButton.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarSeparator

Prop Type Default Description
Class string "" Class adds classes to the root element.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

sidebar.SidebarTrigger

Prop Type Default Description
Controls string "gx-sidebar" Controls is the id that aria-controls names. An empty value is gx-sidebar, the id the shell runtime shows and hides.
Class string "" Class adds classes to the root element.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

Do and do not

Do

  • Give the sidebar the id gx-sidebar when it pairs with SidebarTrigger.
  • Put the text of a menu button in a <span>. A long text is then cut with an ellipsis.
  • Give SidebarMenuAction and SidebarGroupAction a Label.

Don't

  • Do not put a second sidebar on one page.
  • Do not hide the sidebar on a wide screen.

Keyboard

Key Action
Tab Moves between the links and the buttons.
Enter Follows the focused link, or activates the focused button.
Space Activates the focused button.