Skip to content
Gx
GitHub

Toggle Group

A segmented control of toggle items.

<togglegroup.ToggleGroup label="Alignment">
  <togglegroup.ToggleGroupItem name="align" value="left" checked>Left</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align" value="center">Center</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align" value="right">Right</togglegroup.ToggleGroupItem>
</togglegroup.ToggleGroup>

Installation

Run the command in the app module.

Terminal
gx add toggle-group

The command writes these files.

File Path in the app
ToggleGroup.gx ui/toggle-group/ToggleGroup.gx
ToggleGroupItem.gx ui/toggle-group/ToggleGroupItem.gx
styles.go ui/toggle-group/styles.go
ToggleGroup.fixtures.go ui/toggle-group/ToggleGroup.fixtures.go
ToggleGroupItem.fixtures.go ui/toggle-group/ToggleGroupItem.fixtures.go

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

ui/toggle-group/ToggleGroup.gx
package togglegroup

props {
  // Variant sets the visual style of every item: Default or Outline.
  Variant  Variant = Default
  // Size sets the height of every item: Sm, Md or Lg.
  Size     Size = Md
  // Spacing is the gap between the items in spacing units. Zero joins the items.
  Spacing  int = 0
  // Label is the accessible name of the group.
  Label    string = ""
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the toggle items.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<div role="group" aria-label={p.Label} data-variant={p.variant()} data-size={p.size()} data-spacing={p.spacing()} style={p.gap()} class={gx.Cx("group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md", p.Class)} {...p.Attrs}>{p.Children}</div>
ui/toggle-group/ToggleGroupItem.gx
package togglegroup

props {
  // Name is the form field name. Every item of an exclusive group has the same name.
  Name     string
  // Value is the value the form submits when the item is checked.
  Value    string
  // Checked selects the item for the first render.
  Checked  bool = false
  // Multiple renders a checkbox in place of a radio, so the item toggles on its own.
  Multiple bool = false
  // Disabled disables the item.
  Disabled bool = false
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the item label.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<label class={gx.Cx("inline-flex h-9 w-auto min-w-0 shrink-0 items-center justify-center gap-2 rounded-md bg-transparent px-3 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground has-[:focus-visible]:z-10 has-[:focus-visible]:border-ring has-[:focus-visible]:ring-[3px] has-[:focus-visible]:ring-ring/50 has-[:disabled]:pointer-events-none has-[:disabled]:opacity-50 has-[:checked]:bg-accent has-[:checked]:text-accent-foreground motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 group-data-[size=sm]/toggle-group:h-8 group-data-[size=lg]/toggle-group:h-10 group-data-[variant=outline]/toggle-group:border group-data-[variant=outline]/toggle-group:border-input group-data-[variant=outline]/toggle-group:shadow-xs group-data-[variant=outline]/toggle-group:hover:bg-accent group-data-[variant=outline]/toggle-group:hover:text-accent-foreground group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:first:rounded-l-md group-data-[spacing=0]/toggle-group:last:rounded-r-md group-data-[spacing=0]/toggle-group:group-data-[variant=outline]/toggle-group:border-l-0 group-data-[spacing=0]/toggle-group:group-data-[variant=outline]/toggle-group:shadow-none group-data-[spacing=0]/toggle-group:group-data-[variant=outline]/toggle-group:first:border-l", p.Class)} {...p.Attrs}>
  <input type={p.inputType()} name={p.Name} value={p.Value} checked={p.Checked} disabled={p.Disabled} class="peer sr-only" />
  {p.Children}
</label>
ui/toggle-group/styles.go
package togglegroup

import (
	"strconv"

	"github.com/alternayte/gx"
)

// Variant is the visual style of the items of a toggle group.
type Variant string

// The variants of togglegroup.ToggleGroup.
const (
	Default Variant = "default"
	Outline Variant = "outline"
)

// Size is the height of the items of a toggle group.
type Size string

// The sizes of togglegroup.ToggleGroup.
const (
	Sm Size = "sm"
	Md Size = "md"
	Lg Size = "lg"
)

// variant returns the data-variant value; a zero value is Default. The
// items read it through the group, so one prop styles every item.
func (p ToggleGroupProps) variant() string {
	if p.Variant == "" {
		return string(Default)
	}
	return string(p.Variant)
}

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

// spacing returns the gap between the items in spacing units. Zero joins
// the items.
func (p ToggleGroupProps) spacing() int {
	if p.Spacing < 0 {
		return 0
	}
	return p.Spacing
}

// gap returns the custom property that the gap class of the group reads.
func (p ToggleGroupProps) gap() gx.Style {
	return gx.Style("--gap: " + strconv.Itoa(p.spacing()))
}

// inputType returns the type of the native input: a radio for an exclusive
// choice, a checkbox when the group takes many values.
func (p ToggleGroupItemProps) inputType() string {
	if p.Multiple {
		return "checkbox"
	}
	return "radio"
}
ui/toggle-group/ToggleGroup.fixtures.go
package togglegroup

import "github.com/alternayte/gx"

// align returns three exclusive items. Each fixture passes its own name:
// radios that share a name form one group across the whole gallery page.
func align(name string) gx.Node {
	return gx.Frag(
		ToggleGroupItem(ToggleGroupItemProps{Name: name, Value: "left", Checked: true, Children: gx.Text("Left")}),
		ToggleGroupItem(ToggleGroupItemProps{Name: name, Value: "center", Children: gx.Text("Center")}),
		ToggleGroupItem(ToggleGroupItemProps{Name: name, Value: "right", Children: gx.Text("Right")}),
	)
}

var ToggleGroupFixtures = gx.Fixtures[ToggleGroupProps]{
	"Three":         {Label: "Alignment", Children: align("align")},
	"Outline":       {Label: "Alignment", Variant: Outline, Children: align("align-outline")},
	"Spaced":        {Label: "Alignment", Spacing: 2, Children: align("align-spaced")},
	"OutlineSpaced": {Label: "Alignment", Variant: Outline, Spacing: 2, Children: align("align-outline-spaced")},
	"Small":         {Label: "Alignment", Variant: Outline, Size: Sm, Children: align("align-small")},
	"Large":         {Label: "Alignment", Variant: Outline, Size: Lg, Children: align("align-large")},
	"Multiple": {Label: "Format", Variant: Outline, Children: gx.Frag(
		ToggleGroupItem(ToggleGroupItemProps{Name: "bold", Value: "on", Multiple: true, Checked: true, Children: gx.Text("Bold")}),
		ToggleGroupItem(ToggleGroupItemProps{Name: "italic", Value: "on", Multiple: true, Checked: true, Children: gx.Text("Italic")}),
		ToggleGroupItem(ToggleGroupItemProps{Name: "strike", Value: "on", Multiple: true, Disabled: true, Children: gx.Text("Strike")}),
	)},
}
ui/toggle-group/ToggleGroupItem.fixtures.go
package togglegroup

import "github.com/alternayte/gx"

var ToggleGroupItemFixtures = gx.Fixtures[ToggleGroupItemProps]{
	"Checked":  {Name: "align-demo", Value: "left", Checked: true, Children: gx.Text("Left")},
	"Disabled": {Name: "align-disabled", Value: "left", Disabled: true, Children: gx.Text("Left")},
}

// ToggleGroupItemWrap renders the item inside a group, as a page uses it.
func ToggleGroupItemWrap(n gx.Node) gx.Node {
	return ToggleGroup(ToggleGroupProps{Label: "Alignment", Children: n})
}

The theme must define these tokens: --input, --muted, --muted-foreground, --accent, --accent-foreground, --ring.

Usage

<togglegroup.ToggleGroup label="Alignment" variant={togglegroup.Outline}>
  <togglegroup.ToggleGroupItem name="align" value="left" checked={true}>Left</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align" value="center">Center</togglegroup.ToggleGroupItem>
</togglegroup.ToggleGroup>

The group is a set of styled radio inputs, so it works without JavaScript. Variant and Size on the group style every item. Spacing is the gap between the items in spacing units. Zero joins the items and shares their borders. Set Multiple on every item to make each item an independent checkbox.

Examples

ToggleGroup: Three

<togglegroup.ToggleGroup label="Alignment">
  <togglegroup.ToggleGroupItem name="align" value="left" checked>Left</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align" value="center">Center</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align" value="right">Right</togglegroup.ToggleGroupItem>
</togglegroup.ToggleGroup>

ToggleGroup: Outline

<togglegroup.ToggleGroup label="Alignment" variant={togglegroup.Outline}>
  <togglegroup.ToggleGroupItem name="align-outline" value="left" checked>Left</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align-outline" value="center">Center</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align-outline" value="right">Right</togglegroup.ToggleGroupItem>
</togglegroup.ToggleGroup>

ToggleGroup: Spaced

<togglegroup.ToggleGroup label="Alignment" spacing={2}>
  <togglegroup.ToggleGroupItem name="align-spaced" value="left" checked>Left</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align-spaced" value="center">Center</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align-spaced" value="right">Right</togglegroup.ToggleGroupItem>
</togglegroup.ToggleGroup>

ToggleGroup: Outline spaced

<togglegroup.ToggleGroup label="Alignment" variant={togglegroup.Outline} spacing={2}>
  <togglegroup.ToggleGroupItem name="align-outline-spaced" value="left" checked>Left</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align-outline-spaced" value="center">Center</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align-outline-spaced" value="right">Right</togglegroup.ToggleGroupItem>
</togglegroup.ToggleGroup>

ToggleGroup: Small

<togglegroup.ToggleGroup label="Alignment" variant={togglegroup.Outline} size={togglegroup.Sm}>
  <togglegroup.ToggleGroupItem name="align-small" value="left" checked>Left</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align-small" value="center">Center</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align-small" value="right">Right</togglegroup.ToggleGroupItem>
</togglegroup.ToggleGroup>

ToggleGroup: Large

<togglegroup.ToggleGroup label="Alignment" variant={togglegroup.Outline} size={togglegroup.Lg}>
  <togglegroup.ToggleGroupItem name="align-large" value="left" checked>Left</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align-large" value="center">Center</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="align-large" value="right">Right</togglegroup.ToggleGroupItem>
</togglegroup.ToggleGroup>

ToggleGroup: Multiple

<togglegroup.ToggleGroup label="Format" variant={togglegroup.Outline}>
  <togglegroup.ToggleGroupItem name="bold" value="on" multiple checked>Bold</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="italic" value="on" multiple checked>Italic</togglegroup.ToggleGroupItem>
  <togglegroup.ToggleGroupItem name="strike" value="on" multiple disabled>Strike</togglegroup.ToggleGroupItem>
</togglegroup.ToggleGroup>

ToggleGroupItem: Checked

<togglegroup.ToggleGroupItem name="align-demo" value="left" checked>Left</togglegroup.ToggleGroupItem>

ToggleGroupItem: Disabled

<togglegroup.ToggleGroupItem name="align-disabled" value="left" disabled>Left</togglegroup.ToggleGroupItem>

API reference

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

togglegroup.ToggleGroup

Prop Type Default Description
Variant Variant Default Variant sets the visual style of every item: Default or Outline.
Size Size Md Size sets the height of every item: Sm, Md or Lg.
Spacing int 0 Spacing is the gap between the items in spacing units. Zero joins the items.
Label string "" Label is the accessible name of the group.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the toggle items.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

togglegroup.ToggleGroupItem

Prop Type Default Description
Name string Required Name is the form field name. Every item of an exclusive group has the same name.
Value string Required Value is the value the form submits when the item is checked.
Checked bool false Checked selects the item for the first render.
Multiple bool false Multiple renders a checkbox in place of a radio, so the item toggles on its own.
Disabled bool false Disabled disables the item.
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the item label.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

Do and do not

Do

  • Give every item of an exclusive group the same Name.
  • Set Checked on exactly one item of an exclusive group.
  • Give the group a Label.

Don't

  • Do not mix exclusive items and Multiple items in one group.
  • Do not mix two groups under one name.

Keyboard

Key Action
Tab Moves focus to the checked item. With Multiple, moves focus to each item.
Arrow keys Moves the choice and selects it.
Space Selects the focused item. With Multiple, toggles it.