Button Group
A row of related buttons.
<buttongroup.ButtonGroup>
<button.Button variant={button.Outline}>One</button.Button>
<button.Button variant={button.Outline}>Two</button.Button>
<button.Button variant={button.Outline}>Three</button.Button>
</buttongroup.ButtonGroup>Installation
Run the command in the app module.
gx add button-groupThe command also installs button and icons.
The command writes these files.
| File | Path in the app |
|---|---|
ButtonGroup.gx |
ui/button-group/ButtonGroup.gx |
ButtonGroup.fixtures.go |
ui/button-group/ButtonGroup.fixtures.go |
ButtonGroupText.gx |
ui/button-group/ButtonGroupText.gx |
ButtonGroupText.fixtures.go |
ui/button-group/ButtonGroupText.fixtures.go |
ButtonGroupSeparator.gx |
ui/button-group/ButtonGroupSeparator.gx |
ButtonGroupSeparator.fixtures.go |
ui/button-group/ButtonGroupSeparator.fixtures.go |
styles.go |
ui/button-group/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.
package buttongroup
props {
// Orientation sets the direction of the group: Horizontal for a row or Vertical for a column.
Orientation Orientation = Horizontal
// Children is the content of the group: the buttons, inputs, separators and text.
Children gx.Node
// Class adds classes to the root element.
Class string = ""
// Attrs adds HTML attributes to the root element.
Attrs gx.Attrs = nil
}
<div role="group" data-slot="button-group" data-orientation={p.orientation()} class={gx.Cx("flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", orientationClass[Orientation(p.orientation())], p.Class)} {...p.Attrs}>{p.Children}</div>package buttongroup
import (
"github.com/alternayte/gx"
"github.com/alternayte/gx/registry/button"
"github.com/alternayte/gx/registry/icons"
)
var ButtonGroupFixtures = gx.Fixtures[ButtonGroupProps]{
"Three": {Children: gx.Frag(
button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("One")}),
button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("Two")}),
button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("Three")}),
)},
"Vertical": {Orientation: Vertical, Children: gx.Frag(
button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("One")}),
button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("Two")}),
button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("Three")}),
)},
"Separator": {Children: gx.Frag(
button.Button(button.ButtonProps{Variant: button.Secondary, Children: gx.Text("Copy")}),
ButtonGroupSeparator(ButtonGroupSeparatorProps{}),
button.Button(button.ButtonProps{Variant: button.Secondary, Size: button.Icon, Attrs: gx.Attrs{{Key: "aria-label", Value: "More"}}, Children: icons.ChevronDown(icons.ChevronDownProps{})}),
)},
"Text": {Children: gx.Frag(
ButtonGroupText(ButtonGroupTextProps{Children: gx.Text("https://")}),
button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("example.com")}),
)},
"Nested": {Children: gx.Frag(
ButtonGroup(ButtonGroupProps{Children: gx.Frag(
button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("One")}),
button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("Two")}),
)}),
ButtonGroup(ButtonGroupProps{Children: button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("Three")})}),
)},
}package buttongroup
props {
// For is the id of an input. A non-empty value renders the text as the label of that input.
For string = ""
// Class adds classes to the root element.
Class string = ""
// Children is the content of the text block.
Children gx.Node
// Attrs adds HTML attributes to the root element.
Attrs gx.Attrs = nil
}
if p.For != "" {
<label for={p.For} class={gx.Cx("flex items-center gap-2 rounded-md border border-border bg-muted px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4", p.Class)} {...p.Attrs}>{p.Children}</label>
} else {
<div class={gx.Cx("flex items-center gap-2 rounded-md border border-border bg-muted px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4", p.Class)} {...p.Attrs}>{p.Children}</div>
}package buttongroup
import "github.com/alternayte/gx"
var ButtonGroupTextFixtures = gx.Fixtures[ButtonGroupTextProps]{"Text": {Children: gx.Text("https://")}}package buttongroup
props {
// Orientation sets the direction of the line: Vertical or Horizontal. Use Vertical in a
// horizontal group.
Orientation Orientation = Vertical
// Class adds classes to the root element.
Class string = ""
// Attrs adds HTML attributes to the root element.
Attrs gx.Attrs = nil
}
<div data-slot="button-group-separator" role="none" data-orientation={p.orientation()} class={gx.Cx("shrink-0 bg-border", separatorClass[Orientation(p.orientation())], "relative m-0! self-stretch bg-input", p.Class)} {...p.Attrs}></div>package buttongroup
import (
"github.com/alternayte/gx"
"github.com/alternayte/gx/registry/button"
)
var ButtonGroupSeparatorFixtures = gx.Fixtures[ButtonGroupSeparatorProps]{"Default": {}}
// ButtonGroupSeparatorWrap renders the separator between two buttons, as a
// group uses it.
func ButtonGroupSeparatorWrap(n gx.Node) gx.Node {
return ButtonGroup(ButtonGroupProps{Children: gx.Frag(
button.Button(button.ButtonProps{Variant: button.Secondary, Children: gx.Text("One")}),
n,
button.Button(button.ButtonProps{Variant: button.Secondary, Children: gx.Text("Two")}),
)})
}package buttongroup
import "github.com/alternayte/gx"
// Orientation is the direction of a button group or of its separator.
type Orientation string
// The orientations of buttongroup.ButtonGroup and
// buttongroup.ButtonGroupSeparator.
const (
Horizontal Orientation = "horizontal"
Vertical Orientation = "vertical"
)
var orientationClass = gx.Enum[Orientation]{
Horizontal: "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
Vertical: "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
}
// separatorClass sizes the separator: a vertical line takes the height of
// the group, a horizontal line takes its width.
var separatorClass = gx.Enum[Orientation]{
Horizontal: "h-px w-full",
Vertical: "h-auto w-px",
}
// orientation returns the data-orientation value; a zero value is
// Horizontal.
func (p ButtonGroupProps) orientation() string {
if p.Orientation == "" {
return string(Horizontal)
}
return string(p.Orientation)
}
// orientation returns the data-orientation value; a zero value is
// Vertical, the line between the buttons of a horizontal group.
func (p ButtonGroupSeparatorProps) orientation() string {
if p.Orientation == "" {
return string(Vertical)
}
return string(p.Orientation)
}The theme must define these tokens: --ring, --border, --input, --muted.
Usage
A row or a column of related buttons.
<buttongroup.ButtonGroup>
<button.Button variant={button.Outline}>One</button.Button>
<button.Button variant={button.Outline}>Two</button.Button>
</buttongroup.ButtonGroup>
<buttongroup.ButtonGroup>
<button.Button variant={button.Secondary}>Copy</button.Button>
<buttongroup.ButtonGroupSeparator />
<button.Button variant={button.Secondary} size={button.Icon}><icons.ChevronDown /></button.Button>
</buttongroup.ButtonGroup>Set orientation={buttongroup.Vertical} for a column.
ButtonGroupText shows text or a label next to a button or an input. Set For to make it the label of an input.
ButtonGroupSeparator draws a line between two buttons that have no border.
A group in a group makes a gap between the inner groups.
Examples
ButtonGroup: Three
<buttongroup.ButtonGroup>
<button.Button variant={button.Outline}>One</button.Button>
<button.Button variant={button.Outline}>Two</button.Button>
<button.Button variant={button.Outline}>Three</button.Button>
</buttongroup.ButtonGroup>ButtonGroup: Vertical
<buttongroup.ButtonGroup orientation={buttongroup.Vertical}>
<button.Button variant={button.Outline}>One</button.Button>
<button.Button variant={button.Outline}>Two</button.Button>
<button.Button variant={button.Outline}>Three</button.Button>
</buttongroup.ButtonGroup>ButtonGroup: Separator
<buttongroup.ButtonGroup>
<button.Button variant={button.Secondary}>Copy</button.Button>
<buttongroup.ButtonGroupSeparator />
<button.Button variant={button.Secondary} size={button.Icon} attrs={gx.Attrs{{Key: "aria-label", Value: "More"}}}>
<icons.ChevronDown />
</button.Button>
</buttongroup.ButtonGroup>ButtonGroup: Text
<buttongroup.ButtonGroup>
<buttongroup.ButtonGroupText>https://</buttongroup.ButtonGroupText>
<button.Button variant={button.Outline}>example.com</button.Button>
</buttongroup.ButtonGroup>ButtonGroup: Nested
<buttongroup.ButtonGroup>
<buttongroup.ButtonGroup>
<button.Button variant={button.Outline}>One</button.Button>
<button.Button variant={button.Outline}>Two</button.Button>
</buttongroup.ButtonGroup>
<buttongroup.ButtonGroup><button.Button variant={button.Outline}>Three</button.Button></buttongroup.ButtonGroup>
</buttongroup.ButtonGroup>ButtonGroupSeparator: Default
<buttongroup.ButtonGroupSeparator />ButtonGroupText: Text
<buttongroup.ButtonGroupText>https://</buttongroup.ButtonGroupText>API reference
A tag sets a prop by its name with a lower-case first letter: Class is class.
buttongroup.ButtonGroup
| Prop | Type | Default | Description |
|---|---|---|---|
Orientation |
Orientation |
Horizontal |
Orientation sets the direction of the group: Horizontal for a row or Vertical for a column. |
Children |
gx.Node |
Required | Children is the content of the group: the buttons, inputs, separators and text. |
Class |
string |
"" |
Class adds classes to the root element. |
Attrs |
gx.Attrs |
nil |
Attrs adds HTML attributes to the root element. |
buttongroup.ButtonGroupSeparator
| Prop | Type | Default | Description |
|---|---|---|---|
Orientation |
Orientation |
Vertical |
Orientation sets the direction of the line: Vertical or Horizontal. Use Vertical in a horizontal group. |
Class |
string |
"" |
Class adds classes to the root element. |
Attrs |
gx.Attrs |
nil |
Attrs adds HTML attributes to the root element. |
buttongroup.ButtonGroupText
| Prop | Type | Default | Description |
|---|---|---|---|
For |
string |
"" |
For is the id of an input. A non-empty value renders the text as the label of that input. |
Class |
string |
"" |
Class adds classes to the root element. |
Children |
gx.Node |
Required | Children is the content of the text block. |
Attrs |
gx.Attrs |
nil |
Attrs adds HTML attributes to the root element. |
Do and do not
Do
- Give the group an
aria-labelwhen its purpose is not clear from the buttons. - Use the same variant for every button of a group.
Don't
- Do not mix button sizes in one group.
- Do not put a separator between outline buttons. Their borders divide them.
Keyboard
| Key | Action |
|---|---|
| Tab | Moves focus from one button to the next. |
| Enter | Activates the focused button. |
| Space | Activates the focused button. |