Skip to content
Gx
GitHub

Dropdown Menu

A menu of actions behind a trigger.

<dropdownmenu.DropdownMenu id="demo-dropdown" class="w-56">
  <dropdownmenu.DropdownMenuLabel>My account</dropdownmenu.DropdownMenuLabel>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuGroup label="Account">
    <dropdownmenu.DropdownMenuItem>Profile</dropdownmenu.DropdownMenuItem>
    <dropdownmenu.DropdownMenuItem>
      Settings
      <dropdownmenu.DropdownMenuShortcut>⌘S</dropdownmenu.DropdownMenuShortcut>
    </dropdownmenu.DropdownMenuItem>
    <dropdownmenu.DropdownMenuLink href={gx.URL("/docs")}>Documentation</dropdownmenu.DropdownMenuLink>
  </dropdownmenu.DropdownMenuGroup>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuCheckboxItem name="status-bar" checked>Status bar</dropdownmenu.DropdownMenuCheckboxItem>
  <dropdownmenu.DropdownMenuCheckboxItem name="panel">Panel</dropdownmenu.DropdownMenuCheckboxItem>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuRadioGroup label="Position">
    <dropdownmenu.DropdownMenuRadioItem name="demo-dropdown-position" value="top" checked>
      Top
    </dropdownmenu.DropdownMenuRadioItem>
    <dropdownmenu.DropdownMenuRadioItem name="demo-dropdown-position" value="bottom">
      Bottom
    </dropdownmenu.DropdownMenuRadioItem>
  </dropdownmenu.DropdownMenuRadioGroup>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuItem variant={dropdownmenu.Destructive}>Sign out</dropdownmenu.DropdownMenuItem>
</dropdownmenu.DropdownMenu>
<dropdownmenu.DropdownMenuTrigger id="demo-dropdown">Open menu</dropdownmenu.DropdownMenuTrigger>

Installation

Run the command in the app module.

Terminal
gx add dropdown-menu

The command also installs button and icons.

The command writes these files.

File Path in the app
DropdownMenu.gx ui/dropdown-menu/DropdownMenu.gx
DropdownMenuCheckboxItem.gx ui/dropdown-menu/DropdownMenuCheckboxItem.gx
DropdownMenuGroup.gx ui/dropdown-menu/DropdownMenuGroup.gx
DropdownMenuItem.gx ui/dropdown-menu/DropdownMenuItem.gx
DropdownMenuLabel.gx ui/dropdown-menu/DropdownMenuLabel.gx
DropdownMenuLink.gx ui/dropdown-menu/DropdownMenuLink.gx
DropdownMenuRadioGroup.gx ui/dropdown-menu/DropdownMenuRadioGroup.gx
DropdownMenuRadioItem.gx ui/dropdown-menu/DropdownMenuRadioItem.gx
DropdownMenuSeparator.gx ui/dropdown-menu/DropdownMenuSeparator.gx
DropdownMenuShortcut.gx ui/dropdown-menu/DropdownMenuShortcut.gx
DropdownMenuSub.gx ui/dropdown-menu/DropdownMenuSub.gx
DropdownMenuSubContent.gx ui/dropdown-menu/DropdownMenuSubContent.gx
DropdownMenuSubTrigger.gx ui/dropdown-menu/DropdownMenuSubTrigger.gx
DropdownMenuTrigger.gx ui/dropdown-menu/DropdownMenuTrigger.gx
DropdownMenu.fixtures.go ui/dropdown-menu/DropdownMenu.fixtures.go
DropdownMenuCheckboxItem.fixtures.go ui/dropdown-menu/DropdownMenuCheckboxItem.fixtures.go
DropdownMenuGroup.fixtures.go ui/dropdown-menu/DropdownMenuGroup.fixtures.go
DropdownMenuItem.fixtures.go ui/dropdown-menu/DropdownMenuItem.fixtures.go
DropdownMenuLabel.fixtures.go ui/dropdown-menu/DropdownMenuLabel.fixtures.go
DropdownMenuLink.fixtures.go ui/dropdown-menu/DropdownMenuLink.fixtures.go
DropdownMenuRadioGroup.fixtures.go ui/dropdown-menu/DropdownMenuRadioGroup.fixtures.go
DropdownMenuRadioItem.fixtures.go ui/dropdown-menu/DropdownMenuRadioItem.fixtures.go
DropdownMenuSeparator.fixtures.go ui/dropdown-menu/DropdownMenuSeparator.fixtures.go
DropdownMenuShortcut.fixtures.go ui/dropdown-menu/DropdownMenuShortcut.fixtures.go
DropdownMenuSub.fixtures.go ui/dropdown-menu/DropdownMenuSub.fixtures.go
DropdownMenuSubContent.fixtures.go ui/dropdown-menu/DropdownMenuSubContent.fixtures.go
DropdownMenuSubTrigger.fixtures.go ui/dropdown-menu/DropdownMenuSubTrigger.fixtures.go
DropdownMenuTrigger.fixtures.go ui/dropdown-menu/DropdownMenuTrigger.fixtures.go
styles.go ui/dropdown-menu/styles.go

Install button and icons 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/dropdown-menu/DropdownMenu.gx
package dropdownmenu

props {
  // Id is the id of the root element. The trigger opens the menu by this id.
  Id       string
  // Align sets the edge of the trigger that the menu lines up with: Center, Start or End.
  Align    Align = Center
  // 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
}

<div id={p.Id} popover="auto" role="menu" data-gx-dismiss data-gx-roving="nowrap" data-gx-place={p.place()} style={p.style()} class={gx.Cx("z-50 min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md outline-hidden", motionClass, alignClass[p.align()], p.Class)} {...p.Attrs}>{p.Children}</div>
ui/dropdown-menu/DropdownMenuCheckboxItem.gx
package dropdownmenu

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

props {
  // Name is the name attribute of the checkbox input. A form or a signal reads it.
  Name     string = ""
  // Value is the value that the form sends when the item is checked.
  Value    string = "on"
  // Checked sets the item as checked on the first render.
  Checked  bool = false
  // Disabled turns the item off. The arrow keys pass it.
  Disabled bool = false
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the label of the item.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<label class={gx.Cx("relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm select-none hover:bg-accent hover:text-accent-foreground has-[:focus]:bg-accent has-[:focus]:text-accent-foreground has-[:disabled]:pointer-events-none has-[:disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", p.Class)} {...p.Attrs}>
  <input type="checkbox" role="menuitemcheckbox" tabindex="-1" name={p.Name} value={p.Value} checked={p.Checked} disabled={p.Disabled} class="peer sr-only" {...rovingItem(p.Disabled)} />
  <span class="pointer-events-none invisible absolute left-2 flex size-3.5 items-center justify-center peer-checked:visible"><icons.Check class="size-4" /></span>
  {p.Children}
</label>
ui/dropdown-menu/DropdownMenuGroup.gx
package dropdownmenu

props {
  // Label is the accessible name of the group. It is not visible.
  Label    string = ""
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the items of the group.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div role="group" aria-label={p.Label} class={p.Class} {...p.Attrs}>{p.Children}</div>
ui/dropdown-menu/DropdownMenuItem.gx
package dropdownmenu

props {
  // Variant sets the visual style: Default or Destructive.
  Variant  Variant = Default
  // Inset adds left padding, so the item lines up with the checkbox and radio items.
  Inset    bool = false
  // Disabled turns the item off. The arrow keys pass it.
  Disabled bool = false
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the item.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<button type="button" role="menuitem" tabindex="-1" disabled={p.Disabled} data-gx-close class={gx.Cx("relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-hidden select-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", variantClass[p.variant()], insetClass[p.Inset], p.Class)} {...rovingItem(p.Disabled)} {...p.Attrs}>{p.Children}</button>
ui/dropdown-menu/DropdownMenuLabel.gx
package dropdownmenu

props {
  // Inset adds left padding, so the label lines up with the checkbox and radio items.
  Inset    bool = false
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the text of the label.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div role="presentation" class={gx.Cx("px-2 py-1.5 text-sm font-medium", insetClass[p.Inset], p.Class)} {...p.Attrs}>{p.Children}</div>
ui/dropdown-menu/DropdownMenuLink.gx
package dropdownmenu

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

<a role="menuitem" tabindex="-1" href={p.Href} data-gx-roving-item data-gx-close class={gx.Cx("relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm no-underline outline-hidden select-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", p.Class)} {...p.Attrs}>{p.Children}</a>
ui/dropdown-menu/DropdownMenuRadioGroup.gx
package dropdownmenu

props {
  // Label is the accessible name of the group. It is not visible.
  Label    string = ""
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the radio items of the group.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div role="group" aria-label={p.Label} class={p.Class} {...p.Attrs}>{p.Children}</div>
ui/dropdown-menu/DropdownMenuRadioItem.gx
package dropdownmenu

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

props {
  // Name is the name attribute of the radio input. The radio items of one group share
  // the same name.
  Name     string = ""
  // Value is the value that the form sends when the item is checked.
  Value    string = ""
  // Checked sets the item as checked on the first render.
  Checked  bool = false
  // Disabled turns the item off. The arrow keys pass it.
  Disabled bool = false
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the label of the item.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<label class={gx.Cx("relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm select-none hover:bg-accent hover:text-accent-foreground has-[:focus]:bg-accent has-[:focus]:text-accent-foreground has-[:disabled]:pointer-events-none has-[:disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", p.Class)} {...p.Attrs}>
  <input type="radio" role="menuitemradio" tabindex="-1" name={p.Name} value={p.Value} checked={p.Checked} disabled={p.Disabled} class="peer sr-only" {...rovingItem(p.Disabled)} />
  <span class="pointer-events-none invisible absolute left-2 flex size-3.5 items-center justify-center peer-checked:visible"><icons.Circle class="size-2 *:fill-current" /></span>
  {p.Children}
</label>
ui/dropdown-menu/DropdownMenuSeparator.gx
package dropdownmenu

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

<div role="separator" class={gx.Cx("-mx-1 my-1 h-px bg-border", p.Class)} {...p.Attrs}></div>
ui/dropdown-menu/DropdownMenuShortcut.gx
package dropdownmenu

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

<span class={gx.Cx("ml-auto text-xs tracking-widest text-muted-foreground", p.Class)} {...p.Attrs}>{p.Children}</span>
ui/dropdown-menu/DropdownMenuSub.gx
package dropdownmenu

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the sub-menu trigger, followed by the sub-menu content.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div role="none" data-gx-sub class={p.Class} {...p.Attrs}>{p.Children}</div>
ui/dropdown-menu/DropdownMenuSubContent.gx
package dropdownmenu

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

<div popover="auto" role="menu" data-gx-roving="nowrap" data-gx-place="right start" class={gx.Cx("z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg outline-hidden", subMotionClass, p.Class)} {...p.Attrs}>{p.Children}</div>
ui/dropdown-menu/DropdownMenuSubTrigger.gx
package dropdownmenu

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

props {
  // Inset adds left padding, so the trigger lines up with the checkbox and radio items.
  Inset    bool = false
  // Disabled turns the trigger off. The arrow keys pass it.
  Disabled bool = false
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the label of the trigger. A chevron follows it.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<button type="button" role="menuitem" tabindex="-1" aria-haspopup="menu" aria-expanded="false" disabled={p.Disabled} class={gx.Cx("flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-hidden select-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground aria-expanded:bg-accent aria-expanded:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", insetClass[p.Inset], p.Class)} {...rovingItem(p.Disabled)} {...p.Attrs}>
  {p.Children}
  <icons.ChevronRight class="ml-auto size-4" />
</button>
ui/dropdown-menu/DropdownMenuTrigger.gx
package dropdownmenu

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

props {
  // Id is the id of the menu that the trigger opens.
  Id       string
  // Variant sets the visual style of the button: button.Default, button.Secondary,
  // button.Destructive, button.Outline, button.Ghost or button.Link.
  Variant  button.Variant = button.Outline
  // Size sets the height and padding of the button: button.Xs, button.Sm, button.Md, button.Lg,
  // or an Icon size for a square button.
  Size     button.Size = button.Md
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the button label.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<button.Button variant={p.variant()} size={p.Size} class={p.Class} attrs={p.attrs()}>{p.Children}</button.Button>
ui/dropdown-menu/DropdownMenu.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuFixtures = gx.Fixtures[DropdownMenuProps]{
	"Menu": {Id: "demo-dropdown", Class: "w-56", Children: gx.Frag(
		DropdownMenuLabel(DropdownMenuLabelProps{Children: gx.Text("My account")}),
		DropdownMenuSeparator(DropdownMenuSeparatorProps{}),
		DropdownMenuGroup(DropdownMenuGroupProps{Label: "Account", Children: gx.Frag(
			DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Profile")}),
			DropdownMenuItem(DropdownMenuItemProps{Children: gx.Frag(
				gx.Text("Settings"),
				DropdownMenuShortcut(DropdownMenuShortcutProps{Children: gx.Text("⌘S")}),
			)}),
			DropdownMenuLink(DropdownMenuLinkProps{Href: gx.URL("/docs"), Children: gx.Text("Documentation")}),
		)}),
		DropdownMenuSeparator(DropdownMenuSeparatorProps{}),
		DropdownMenuCheckboxItem(DropdownMenuCheckboxItemProps{Name: "status-bar", Checked: true, Children: gx.Text("Status bar")}),
		DropdownMenuCheckboxItem(DropdownMenuCheckboxItemProps{Name: "panel", Children: gx.Text("Panel")}),
		DropdownMenuSeparator(DropdownMenuSeparatorProps{}),
		DropdownMenuRadioGroup(DropdownMenuRadioGroupProps{Label: "Position", Children: gx.Frag(
			DropdownMenuRadioItem(DropdownMenuRadioItemProps{Name: "demo-dropdown-position", Value: "top", Checked: true, Children: gx.Text("Top")}),
			DropdownMenuRadioItem(DropdownMenuRadioItemProps{Name: "demo-dropdown-position", Value: "bottom", Children: gx.Text("Bottom")}),
		)}),
		DropdownMenuSeparator(DropdownMenuSeparatorProps{}),
		DropdownMenuItem(DropdownMenuItemProps{Variant: Destructive, Children: gx.Text("Sign out")}),
	)},
	"End": {Id: "demo-dropdown-end", Align: End, Children: gx.Frag(
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Rename")}),
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Duplicate")}),
		DropdownMenuItem(DropdownMenuItemProps{Disabled: true, Children: gx.Text("Archive")}),
		DropdownMenuItem(DropdownMenuItemProps{Variant: Destructive, Children: gx.Text("Delete")}),
	)},
	"Start": {Id: "demo-dropdown-start", Align: Start, Children: gx.Frag(
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Rename")}),
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Duplicate")}),
	)},
	"Sub": {Id: "demo-dropdown-sub", Class: "w-56", Children: gx.Frag(
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("New tab")}),
		DropdownMenuSub(DropdownMenuSubProps{Children: gx.Frag(
			DropdownMenuSubTrigger(DropdownMenuSubTriggerProps{Children: gx.Text("More tools")}),
			DropdownMenuSubContent(DropdownMenuSubContentProps{Class: "w-48", Children: gx.Frag(
				DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Save page")}),
				DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Create shortcut")}),
				DropdownMenuSeparator(DropdownMenuSeparatorProps{}),
				DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Developer tools")}),
			)}),
		)}),
		DropdownMenuSeparator(DropdownMenuSeparatorProps{}),
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Print")}),
	)},
}
ui/dropdown-menu/DropdownMenuCheckboxItem.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuCheckboxItemFixtures = gx.Fixtures[DropdownMenuCheckboxItemProps]{
	"Checked":   {Name: "status-bar", Checked: true, Children: gx.Text("Status bar")},
	"Unchecked": {Name: "panel", Children: gx.Text("Panel")},
	"Disabled":  {Name: "activity", Disabled: true, Children: gx.Text("Activity bar")},
}

// DropdownMenuCheckboxItemWrap renders the item inside a menu, as a page uses it.
func DropdownMenuCheckboxItemWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "role", Value: "menu"}, {Key: "class", Value: "w-56"}}, n)
}
ui/dropdown-menu/DropdownMenuGroup.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuGroupFixtures = gx.Fixtures[DropdownMenuGroupProps]{
	"Default": {Label: "Account", Children: gx.Frag(
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Profile")}),
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Billing")}),
	)},
}

// DropdownMenuGroupWrap renders the group inside a menu, as a page uses it.
func DropdownMenuGroupWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "role", Value: "menu"}, {Key: "class", Value: "w-56"}}, n)
}
ui/dropdown-menu/DropdownMenuItem.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuItemFixtures = gx.Fixtures[DropdownMenuItemProps]{
	"Item":        {Children: gx.Text("Profile")},
	"Inset":       {Inset: true, Children: gx.Text("Profile")},
	"Destructive": {Variant: Destructive, Children: gx.Text("Delete")},
	"Disabled":    {Disabled: true, Children: gx.Text("Profile")},
}

// DropdownMenuItemWrap renders the item inside a menu, as a page uses it.
func DropdownMenuItemWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "role", Value: "menu"}, {Key: "class", Value: "w-56"}}, n)
}
ui/dropdown-menu/DropdownMenuLabel.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuLabelFixtures = gx.Fixtures[DropdownMenuLabelProps]{
	"Label": {Children: gx.Text("My account")},
	"Inset": {Inset: true, Children: gx.Text("My account")},
}
ui/dropdown-menu/DropdownMenuLink.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuLinkFixtures = gx.Fixtures[DropdownMenuLinkProps]{"Link": {Href: gx.URL("/docs"), Children: gx.Text("Documentation")}}

// DropdownMenuLinkWrap renders the item inside a menu, as a page uses it.
func DropdownMenuLinkWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "role", Value: "menu"}, {Key: "class", Value: "w-56"}}, n)
}
ui/dropdown-menu/DropdownMenuRadioGroup.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuRadioGroupFixtures = gx.Fixtures[DropdownMenuRadioGroupProps]{
	"Default": {Label: "Position", Children: gx.Frag(
		DropdownMenuRadioItem(DropdownMenuRadioItemProps{Name: "dropdown-menu-group-position", Value: "top", Checked: true, Children: gx.Text("Top")}),
		DropdownMenuRadioItem(DropdownMenuRadioItemProps{Name: "dropdown-menu-group-position", Value: "bottom", Children: gx.Text("Bottom")}),
	)},
}

// DropdownMenuRadioGroupWrap renders the group inside a menu, as a page uses it.
func DropdownMenuRadioGroupWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "role", Value: "menu"}, {Key: "class", Value: "w-56"}}, n)
}
ui/dropdown-menu/DropdownMenuRadioItem.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuRadioItemFixtures = gx.Fixtures[DropdownMenuRadioItemProps]{
	"Checked":   {Name: "dropdown-menu-item-checked", Value: "top", Checked: true, Children: gx.Text("Top")},
	"Unchecked": {Name: "dropdown-menu-item-unchecked", Value: "bottom", Children: gx.Text("Bottom")},
}

// DropdownMenuRadioItemWrap renders the item inside a menu, as a page uses it.
func DropdownMenuRadioItemWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "role", Value: "menu"}, {Key: "class", Value: "w-56"}}, n)
}
ui/dropdown-menu/DropdownMenuSeparator.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuSeparatorFixtures = gx.Fixtures[DropdownMenuSeparatorProps]{"Default": {}}
ui/dropdown-menu/DropdownMenuShortcut.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuShortcutFixtures = gx.Fixtures[DropdownMenuShortcutProps]{"Default": {Children: gx.Text("⌘K")}}
ui/dropdown-menu/DropdownMenuSub.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuSubFixtures = gx.Fixtures[DropdownMenuSubProps]{
	"Default": {Children: gx.Frag(
		DropdownMenuSubTrigger(DropdownMenuSubTriggerProps{Children: gx.Text("More tools")}),
		DropdownMenuSubContent(DropdownMenuSubContentProps{Children: gx.Frag(
			DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Save page")}),
			DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Create shortcut")}),
		)}),
	)},
}

// DropdownMenuSubWrap renders the sub-menu inside a menu, as a page uses it.
func DropdownMenuSubWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "role", Value: "menu"}, {Key: "class", Value: "w-56"}}, n)
}
ui/dropdown-menu/DropdownMenuSubContent.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuSubContentFixtures = gx.Fixtures[DropdownMenuSubContentProps]{
	"Content": {Class: "w-48", Children: gx.Frag(
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Save page")}),
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Create shortcut")}),
		DropdownMenuSeparator(DropdownMenuSeparatorProps{}),
		DropdownMenuItem(DropdownMenuItemProps{Children: gx.Text("Developer tools")}),
	)},
}

// DropdownMenuSubContentWrap renders the content behind its trigger inside a menu,
// as a page uses it.
func DropdownMenuSubContentWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "role", Value: "menu"}, {Key: "class", Value: "w-56"}},
		DropdownMenuSub(DropdownMenuSubProps{Children: gx.Frag(
			DropdownMenuSubTrigger(DropdownMenuSubTriggerProps{Children: gx.Text("More tools")}),
			n,
		)}),
	)
}
ui/dropdown-menu/DropdownMenuSubTrigger.fixtures.go
package dropdownmenu

import "github.com/alternayte/gx"

var DropdownMenuSubTriggerFixtures = gx.Fixtures[DropdownMenuSubTriggerProps]{
	"Trigger":  {Children: gx.Text("More tools")},
	"Inset":    {Inset: true, Children: gx.Text("More tools")},
	"Disabled": {Disabled: true, Children: gx.Text("More tools")},
}

// DropdownMenuSubTriggerWrap renders the trigger inside a menu, as a page uses it.
func DropdownMenuSubTriggerWrap(n gx.Node) gx.Node {
	return gx.El("div", gx.Attrs{{Key: "role", Value: "menu"}, {Key: "class", Value: "w-56"}}, n)
}
ui/dropdown-menu/DropdownMenuTrigger.fixtures.go
package dropdownmenu

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

var DropdownMenuTriggerFixtures = gx.Fixtures[DropdownMenuTriggerProps]{
	"Default": {Id: "demo-dropdown", Children: gx.Text("Open menu")},
	"Ghost":   {Id: "demo-dropdown-end", Variant: button.Ghost, Size: button.Sm, Class: "ml-32", Children: gx.Text("Open at the end")},
	"Start":   {Id: "demo-dropdown-start", Variant: button.Secondary, Children: gx.Text("Open at the start")},
	"Sub":     {Id: "demo-dropdown-sub", Children: gx.Text("Open with a sub-menu")},
}
ui/dropdown-menu/styles.go
package dropdownmenu

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

// Align is the edge of the trigger that the menu lines up with.
type Align string

// The alignments of dropdownmenu.DropdownMenu.
const (
	Center Align = "center"
	Start  Align = "start"
	End    Align = "end"
)

// The menu zooms from the edge that touches the trigger. The overlay module
// writes data-side: a menu that flips opens above the trigger.
var alignClass = gx.Enum[Align]{
	Center: "origin-top data-[side=top]:origin-bottom",
	Start:  "origin-top-left data-[side=top]:origin-bottom-left",
	End:    "origin-top-right data-[side=top]:origin-bottom-right",
}

var alignStyle = gx.Enum[Align]{
	Center: "justify-self: anchor-center",
	Start:  "left: anchor(left)",
	End:    "right: anchor(right)",
}

// align returns the alignment of one menu; a zero value is Center.
func (p DropdownMenuProps) align() Align {
	if p.Align == "" {
		return Center
	}
	return p.Align
}

// style anchors the menu below its trigger for a page whose scripts did not
// run. The overlay module then writes the measured place.
func (p DropdownMenuProps) style() gx.Style {
	return gx.Style("position-anchor: --gx-menu-" + p.Id + "; inset: auto; margin: 0.25rem 0 0; top: anchor(bottom); " + alignStyle[p.align()])
}

// place tells the overlay module where the menu goes: below the trigger,
// 4px away, and inside the viewport.
func (p DropdownMenuProps) place() string {
	return "bottom " + string(p.align()) + " 4"
}

// variant returns the button variant of one trigger; a zero value is
// button.Outline.
func (p DropdownMenuTriggerProps) variant() button.Variant {
	if p.Variant == "" {
		return button.Outline
	}
	return p.Variant
}

// attrs wires the trigger to its menu and names it as the anchor.
func (p DropdownMenuTriggerProps) attrs() gx.Attrs {
	return gx.JoinAttrs(gx.Attrs{
		{Key: "popovertarget", Value: p.Id},
		{Key: "aria-haspopup", Value: "menu"},
		{Key: "style", Value: "anchor-name: --gx-menu-" + p.Id, Kind: gx.AttrStyle},
	}, p.Attrs)
}

// Variant is the visual style of a menu item.
type Variant string

// The variants of dropdownmenu.DropdownMenuItem.
const (
	Default     Variant = "default"
	Destructive Variant = "destructive"
)

var variantClass = gx.Enum[Variant]{
	Default:     "",
	Destructive: "text-destructive hover:bg-destructive/10 hover:text-destructive focus:bg-destructive/10 focus:text-destructive dark:hover:bg-destructive/20 dark:focus:bg-destructive/20 *:[svg]:text-destructive!",
}

// variant returns the variant of one item; a zero value is Default.
func (p DropdownMenuItemProps) variant() Variant {
	if p.Variant == "" {
		return Default
	}
	return p.Variant
}

// insetClass lines an item or a label up with the checkbox and radio items.
var insetClass = map[bool]string{
	true:  "pl-8",
	false: "",
}

// rovingItem marks an item for the roving tabindex of its menu. A disabled
// item is not marked, so the arrow keys pass it.
func rovingItem(disabled bool) gx.Attrs {
	return gx.Attrs{gx.Bool("data-gx-roving-item", !disabled)}
}

// motionClass fades and zooms the menu from 95% and slides it 2 units from
// the trigger, from below when the menu took the top side. Safari 26.0 never ends a display transition on an element
// that CSS anchor positioning places, which leaves a closed menu rendered.
// The @supports test matches every engine but WebKit, so Safari closes the
// menu at once and still animates the enter.
const motionClass = "opacity-0 scale-95 transition-[opacity,scale,translate,overlay,display] not-supports-[font:-apple-system-body]:transition-discrete duration-150 open:opacity-100 open:scale-100 starting:open:opacity-0 starting:open:scale-95 starting:open:-translate-y-2 data-[side=top]:starting:open:translate-y-2 motion-reduce:transition-none"

// subMotionClass fades and zooms the content of a sub-menu from 95% and
// slides it 2 units from its trigger. The overlay module writes data-side:
// content that flips to the left slides from the right.
const subMotionClass = "origin-top-left opacity-0 scale-95 transition-[opacity,scale,translate,overlay,display] transition-discrete duration-150 open:opacity-100 open:scale-100 starting:open:opacity-0 starting:open:scale-95 starting:open:-translate-x-2 data-[side=left]:origin-top-right data-[side=left]:starting:open:translate-x-2 motion-reduce:transition-none"

The theme must define these tokens: --border, --popover, --popover-foreground, --accent, --accent-foreground, --muted-foreground, --destructive.

Usage

<dropdownmenu.DropdownMenuTrigger id="account">Account</dropdownmenu.DropdownMenuTrigger>
<dropdownmenu.DropdownMenu id="account" class="w-56">
  <dropdownmenu.DropdownMenuLabel>My account</dropdownmenu.DropdownMenuLabel>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuGroup label="Account">
    <dropdownmenu.DropdownMenuLink href={gx.URL("/profile")}>Profile</dropdownmenu.DropdownMenuLink>
    <dropdownmenu.DropdownMenuItem>
      Settings
      <dropdownmenu.DropdownMenuShortcut>⌘S</dropdownmenu.DropdownMenuShortcut>
    </dropdownmenu.DropdownMenuItem>
  </dropdownmenu.DropdownMenuGroup>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuCheckboxItem name="status-bar" checked={true}>Status bar</dropdownmenu.DropdownMenuCheckboxItem>
  <dropdownmenu.DropdownMenuRadioGroup label="Position">
    <dropdownmenu.DropdownMenuRadioItem name="position" value="top" checked={true}>Top</dropdownmenu.DropdownMenuRadioItem>
    <dropdownmenu.DropdownMenuRadioItem name="position" value="bottom">Bottom</dropdownmenu.DropdownMenuRadioItem>
  </dropdownmenu.DropdownMenuRadioGroup>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuSub>
    <dropdownmenu.DropdownMenuSubTrigger>Invite users</dropdownmenu.DropdownMenuSubTrigger>
    <dropdownmenu.DropdownMenuSubContent>
      <dropdownmenu.DropdownMenuItem>Email</dropdownmenu.DropdownMenuItem>
      <dropdownmenu.DropdownMenuItem>Message</dropdownmenu.DropdownMenuItem>
    </dropdownmenu.DropdownMenuSubContent>
  </dropdownmenu.DropdownMenuSub>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuItem variant={dropdownmenu.Destructive}>Sign out</dropdownmenu.DropdownMenuItem>
</dropdownmenu.DropdownMenu>

The menu uses the native Popover API. The trigger is a button.Button; Variant and Size select its style, and the default is button.Outline. Align lines the menu up with the trigger: dropdownmenu.Center (default), dropdownmenu.Start or dropdownmenu.End.

The menu opens below the trigger. It flips above the trigger when the space below is too small, and it shifts along the trigger to stay 8px inside the viewport. data-side on the menu names the side it took.

A checkbox item and a radio item are native inputs. They carry Name and Value, so a form or a signal reads them. They keep the menu open. An item has Inset, Disabled and the Destructive variant.

The menu fades and zooms in and out. Safari shows the enter transition only. A user who asks for reduced motion gets no transition.

DropdownMenuSub holds one DropdownMenuSubTrigger and one DropdownMenuSubContent, in that order. The sub-menu opens to the right of its trigger, or to the left when the right side is too small. It opens on a click and after the pointer rests on the trigger. It stays open while the pointer moves toward it. An item of a sub-menu closes the whole menu. The trigger has Inset and Disabled.

Examples

<dropdownmenu.DropdownMenu id="demo-dropdown" class="w-56">
  <dropdownmenu.DropdownMenuLabel>My account</dropdownmenu.DropdownMenuLabel>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuGroup label="Account">
    <dropdownmenu.DropdownMenuItem>Profile</dropdownmenu.DropdownMenuItem>
    <dropdownmenu.DropdownMenuItem>
      Settings
      <dropdownmenu.DropdownMenuShortcut>⌘S</dropdownmenu.DropdownMenuShortcut>
    </dropdownmenu.DropdownMenuItem>
    <dropdownmenu.DropdownMenuLink href={gx.URL("/docs")}>Documentation</dropdownmenu.DropdownMenuLink>
  </dropdownmenu.DropdownMenuGroup>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuCheckboxItem name="status-bar" checked>Status bar</dropdownmenu.DropdownMenuCheckboxItem>
  <dropdownmenu.DropdownMenuCheckboxItem name="panel">Panel</dropdownmenu.DropdownMenuCheckboxItem>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuRadioGroup label="Position">
    <dropdownmenu.DropdownMenuRadioItem name="demo-dropdown-position" value="top" checked>
      Top
    </dropdownmenu.DropdownMenuRadioItem>
    <dropdownmenu.DropdownMenuRadioItem name="demo-dropdown-position" value="bottom">
      Bottom
    </dropdownmenu.DropdownMenuRadioItem>
  </dropdownmenu.DropdownMenuRadioGroup>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuItem variant={dropdownmenu.Destructive}>Sign out</dropdownmenu.DropdownMenuItem>
</dropdownmenu.DropdownMenu>
<dropdownmenu.DropdownMenuTrigger id="demo-dropdown">Open menu</dropdownmenu.DropdownMenuTrigger>
<dropdownmenu.DropdownMenu id="demo-dropdown-end" align={dropdownmenu.End}>
  <dropdownmenu.DropdownMenuItem>Rename</dropdownmenu.DropdownMenuItem>
  <dropdownmenu.DropdownMenuItem>Duplicate</dropdownmenu.DropdownMenuItem>
  <dropdownmenu.DropdownMenuItem disabled>Archive</dropdownmenu.DropdownMenuItem>
  <dropdownmenu.DropdownMenuItem variant={dropdownmenu.Destructive}>Delete</dropdownmenu.DropdownMenuItem>
</dropdownmenu.DropdownMenu>
<dropdownmenu.DropdownMenuTrigger id="demo-dropdown-end" variant={button.Ghost} size={button.Sm} class="ml-32">
  Open at the end
</dropdownmenu.DropdownMenuTrigger>
<dropdownmenu.DropdownMenu id="demo-dropdown-start" align={dropdownmenu.Start}>
  <dropdownmenu.DropdownMenuItem>Rename</dropdownmenu.DropdownMenuItem>
  <dropdownmenu.DropdownMenuItem>Duplicate</dropdownmenu.DropdownMenuItem>
</dropdownmenu.DropdownMenu>
<dropdownmenu.DropdownMenuTrigger id="demo-dropdown-start" variant={button.Secondary}>
  Open at the start
</dropdownmenu.DropdownMenuTrigger>
<dropdownmenu.DropdownMenu id="demo-dropdown-sub" class="w-56">
  <dropdownmenu.DropdownMenuItem>New tab</dropdownmenu.DropdownMenuItem>
  <dropdownmenu.DropdownMenuSub>
    <dropdownmenu.DropdownMenuSubTrigger>More tools</dropdownmenu.DropdownMenuSubTrigger>
    <dropdownmenu.DropdownMenuSubContent class="w-48">
      <dropdownmenu.DropdownMenuItem>Save page</dropdownmenu.DropdownMenuItem>
      <dropdownmenu.DropdownMenuItem>Create shortcut</dropdownmenu.DropdownMenuItem>
      <dropdownmenu.DropdownMenuSeparator />
      <dropdownmenu.DropdownMenuItem>Developer tools</dropdownmenu.DropdownMenuItem>
    </dropdownmenu.DropdownMenuSubContent>
  </dropdownmenu.DropdownMenuSub>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuItem>Print</dropdownmenu.DropdownMenuItem>
</dropdownmenu.DropdownMenu>
<dropdownmenu.DropdownMenuTrigger id="demo-dropdown-sub">Open with a sub-menu</dropdownmenu.DropdownMenuTrigger>
<dropdownmenu.DropdownMenuCheckboxItem name="status-bar" checked>Status bar</dropdownmenu.DropdownMenuCheckboxItem>
<dropdownmenu.DropdownMenuCheckboxItem name="panel">Panel</dropdownmenu.DropdownMenuCheckboxItem>
<dropdownmenu.DropdownMenuCheckboxItem name="activity" disabled>Activity bar</dropdownmenu.DropdownMenuCheckboxItem>
<dropdownmenu.DropdownMenuGroup label="Account">
  <dropdownmenu.DropdownMenuItem>Profile</dropdownmenu.DropdownMenuItem>
  <dropdownmenu.DropdownMenuItem>Billing</dropdownmenu.DropdownMenuItem>
</dropdownmenu.DropdownMenuGroup>
<dropdownmenu.DropdownMenuItem>Profile</dropdownmenu.DropdownMenuItem>
<dropdownmenu.DropdownMenuItem inset>Profile</dropdownmenu.DropdownMenuItem>
<dropdownmenu.DropdownMenuItem variant={dropdownmenu.Destructive}>Delete</dropdownmenu.DropdownMenuItem>
<dropdownmenu.DropdownMenuItem disabled>Profile</dropdownmenu.DropdownMenuItem>
<dropdownmenu.DropdownMenuLabel>My account</dropdownmenu.DropdownMenuLabel>
<dropdownmenu.DropdownMenuLabel inset>My account</dropdownmenu.DropdownMenuLabel>
<dropdownmenu.DropdownMenuLink href={gx.URL("/docs")}>Documentation</dropdownmenu.DropdownMenuLink>
<dropdownmenu.DropdownMenuRadioGroup label="Position">
  <dropdownmenu.DropdownMenuRadioItem name="dropdown-menu-group-position" value="top" checked>
    Top
  </dropdownmenu.DropdownMenuRadioItem>
  <dropdownmenu.DropdownMenuRadioItem name="dropdown-menu-group-position" value="bottom">
    Bottom
  </dropdownmenu.DropdownMenuRadioItem>
</dropdownmenu.DropdownMenuRadioGroup>
<dropdownmenu.DropdownMenuRadioItem name="dropdown-menu-item-checked" value="top" checked>
  Top
</dropdownmenu.DropdownMenuRadioItem>
<dropdownmenu.DropdownMenuRadioItem name="dropdown-menu-item-unchecked" value="bottom">
  Bottom
</dropdownmenu.DropdownMenuRadioItem>
<dropdownmenu.DropdownMenuSeparator />
<dropdownmenu.DropdownMenuShortcut>⌘K</dropdownmenu.DropdownMenuShortcut>
<dropdownmenu.DropdownMenuSub>
  <dropdownmenu.DropdownMenuSubTrigger>More tools</dropdownmenu.DropdownMenuSubTrigger>
  <dropdownmenu.DropdownMenuSubContent>
    <dropdownmenu.DropdownMenuItem>Save page</dropdownmenu.DropdownMenuItem>
    <dropdownmenu.DropdownMenuItem>Create shortcut</dropdownmenu.DropdownMenuItem>
  </dropdownmenu.DropdownMenuSubContent>
</dropdownmenu.DropdownMenuSub>
<dropdownmenu.DropdownMenuSubContent class="w-48">
  <dropdownmenu.DropdownMenuItem>Save page</dropdownmenu.DropdownMenuItem>
  <dropdownmenu.DropdownMenuItem>Create shortcut</dropdownmenu.DropdownMenuItem>
  <dropdownmenu.DropdownMenuSeparator />
  <dropdownmenu.DropdownMenuItem>Developer tools</dropdownmenu.DropdownMenuItem>
</dropdownmenu.DropdownMenuSubContent>
<dropdownmenu.DropdownMenuSubTrigger>More tools</dropdownmenu.DropdownMenuSubTrigger>
<dropdownmenu.DropdownMenuSubTrigger inset>More tools</dropdownmenu.DropdownMenuSubTrigger>
<dropdownmenu.DropdownMenuSubTrigger disabled>More tools</dropdownmenu.DropdownMenuSubTrigger>

API reference

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

Prop Type Default Description
Id string Required Id is the id of the root element. The trigger opens the menu by this id.
Align Align Center Align sets the edge of the trigger that the menu lines up with: Center, Start or End.
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.
Prop Type Default Description
Name string "" Name is the name attribute of the checkbox input. A form or a signal reads it.
Value string "on" Value is the value that the form sends when the item is checked.
Checked bool false Checked sets the item as checked on the first render.
Disabled bool false Disabled turns the item off. The arrow keys pass it.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the label of the item.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
Prop Type Default Description
Label string "" Label is the accessible name of the group. It is not visible.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the items of the group.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
Prop Type Default Description
Variant Variant Default Variant sets the visual style: Default or Destructive.
Inset bool false Inset adds left padding, so the item lines up with the checkbox and radio items.
Disabled bool false Disabled turns the item off. The arrow keys pass it.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the item.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
Prop Type Default Description
Inset bool false Inset adds left padding, so the label lines up with the checkbox and radio items.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the text of the label.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
Prop Type Default Description
Href gx.URL Required Href is the URL that the link opens.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the link.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
Prop Type Default Description
Label string "" Label is the accessible name of the group. It is not visible.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the radio items of the group.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
Prop Type Default Description
Name string "" Name is the name attribute of the radio input. The radio items of one group share the same name.
Value string "" Value is the value that the form sends when the item is checked.
Checked bool false Checked sets the item as checked on the first render.
Disabled bool false Disabled turns the item off. The arrow keys pass it.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the label of the item.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
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.
Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the text of the keyboard shortcut.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the sub-menu trigger, followed by the sub-menu content.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the list of items of the sub-menu.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
Prop Type Default Description
Inset bool false Inset adds left padding, so the trigger lines up with the checkbox and radio items.
Disabled bool false Disabled turns the trigger off. The arrow keys pass it.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the label of the trigger. A chevron follows it.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.
Prop Type Default Description
Id string Required Id is the id of the menu that the trigger opens.
Variant button.Variant button.Outline Variant sets the visual style of the button: button.Default, button.Secondary, button.Destructive, button.Outline, button.Ghost or button.Link.
Size button.Size button.Md Size sets the height and padding of the button: button.Xs, button.Sm, button.Md, button.Lg, or an Icon size for a square button.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the button label.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

Do and do not

Do

  • Give the menu and the trigger the same Id.
  • Put the menu directly after its trigger in the markup.
  • Give the radio items of one group the same Name.
  • Put destructive actions last, after a separator.

Don't

  • Do not use a dropdown menu as a select control. Use select.
  • Do not put a DropdownMenu in another DropdownMenu. Use DropdownMenuSub.
  • Do not put an element between the trigger and the content of a sub-menu.

Keyboard

Key Action
Enter, Space Opens the menu from the trigger.
Down Opens the menu from the trigger and moves to the first item.
Up, Down Moves through the items. A disabled item is passed.
Home, End Moves to the first or last item.
A letter Moves to the next item that starts with the typed text.
Enter Runs the focused item and closes the menu. Toggles a checkbox or radio item.
Space Runs the focused item. Toggles a checkbox or radio item.
Right, Enter, Space On a sub-menu trigger, opens the sub-menu and moves to its first item.
Left, Escape In a sub-menu, closes it and returns focus to its trigger.
Escape Closes the menu and returns focus to the trigger.