Skip to content
Gx
GitHub

Radio Group

A group of exclusive choices.

<radiogroup.RadioGroup label="Plan">
  <radiogroup.RadioGroupItem name="plan" value="free" checked label="Free" />
  <radiogroup.RadioGroupItem name="plan" value="pro" label="Pro" />
</radiogroup.RadioGroup>

Installation

Run the command in the app module.

Terminal
gx add radio-group

The command also installs icons.

The command writes these files.

File Path in the app
RadioGroup.gx ui/radio-group/RadioGroup.gx
RadioGroupItem.gx ui/radio-group/RadioGroupItem.gx
RadioGroup.fixtures.go ui/radio-group/RadioGroup.fixtures.go
styles.go ui/radio-group/styles.go
RadioGroupItem.fixtures.go ui/radio-group/RadioGroupItem.fixtures.go

Install 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/radio-group/RadioGroup.gx
package radiogroup

props {
  // Orientation sets the layout direction: Vertical or Horizontal.
  Orientation Orientation = Vertical
  // Label is the accessible name of the group.
  Label       string = ""
  // Class adds classes to the root element.
  Class       string = ""
  // Children is the radio items.
  Children    gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs       gx.Attrs = nil
}

<div role="radiogroup" aria-label={p.Label} aria-orientation={p.orientation()} class={gx.Cx("grid gap-3", orientationClass[p.orientation()], p.Class)} {...p.Attrs}>{p.Children}</div>
ui/radio-group/RadioGroupItem.gx
package radiogroup

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

props {
  // Name is the form field name. Every item of one 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
  // Disabled disables the item.
  Disabled bool = false
  // Invalid sets aria-invalid on the input and shows the error style.
  Invalid  bool = false
  // Label is the text beside the radio. An empty value renders no text.
  Label    string = ""
  // Class adds classes to the root element.
  Class    string = ""
  // Children is content beside the radio, after the label text.
  Children gx.Node = nil
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<label class={gx.Cx("flex items-center gap-2 text-sm leading-none font-medium select-none", p.Class)} {...p.Attrs}>
  <input type="radio" name={p.Name} value={p.Value} checked={p.Checked} disabled={p.Disabled} aria-invalid={p.invalid()} class="peer sr-only" />
  <span aria-hidden="true" class="relative aspect-square size-4 shrink-0 rounded-full border border-input text-primary shadow-xs transition-[color,box-shadow] outline-none peer-focus-visible:border-ring peer-focus-visible:ring-[3px] peer-focus-visible:ring-ring/50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50 peer-aria-invalid:border-destructive peer-aria-invalid:ring-destructive/20 dark:bg-input/30 dark:peer-aria-invalid:ring-destructive/40 motion-reduce:transition-none [&>svg]:opacity-0 peer-checked:[&>svg]:opacity-100">
    <icons.Circle class="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 [&>circle]:fill-primary" />
  </span>
  if p.Label != "" {
    <span class="peer-disabled:cursor-not-allowed peer-disabled:opacity-50">{p.Label}</span>
  }
  if p.Children != nil {
    <span class="peer-disabled:cursor-not-allowed peer-disabled:opacity-50">{p.Children}</span>
  }
</label>
ui/radio-group/RadioGroup.fixtures.go
package radiogroup

import "github.com/alternayte/gx"

// Each fixture has its own name: radios that share a name form one group
// across the whole gallery page, and only one of them stays checked.
var RadioGroupFixtures = gx.Fixtures[RadioGroupProps]{
	"Vertical": {
		Label: "Plan",
		Children: gx.Frag(
			RadioGroupItem(RadioGroupItemProps{Name: "plan", Value: "free", Checked: true, Label: "Free"}),
			RadioGroupItem(RadioGroupItemProps{Name: "plan", Value: "pro", Label: "Pro"}),
		),
	},
	"Horizontal": {
		Label:       "Size",
		Orientation: Horizontal,
		Children: gx.Frag(
			RadioGroupItem(RadioGroupItemProps{Name: "size", Value: "s", Checked: true, Label: "Small"}),
			RadioGroupItem(RadioGroupItemProps{Name: "size", Value: "m", Label: "Medium"}),
		),
	},
}
ui/radio-group/styles.go
package radiogroup

import "github.com/alternayte/gx"

// Orientation is the layout direction of a radio group.
type Orientation string

// The orientations of radiogroup.RadioGroup.
const (
	Vertical   Orientation = "vertical"
	Horizontal Orientation = "horizontal"
)

var orientationClass = gx.Enum[Orientation]{
	Vertical:   "grid-cols-1",
	Horizontal: "grid-flow-col auto-cols-max gap-4",
}

// orientation returns the orientation of one group; a zero value is
// Vertical.
func (p RadioGroupProps) orientation() Orientation {
	if p.Orientation == "" {
		return Vertical
	}
	return p.Orientation
}

// invalid returns the aria-invalid value of the input.
func (p RadioGroupItemProps) invalid() string {
	if p.Invalid {
		return "true"
	}
	return "false"
}
ui/radio-group/RadioGroupItem.fixtures.go
package radiogroup

import "github.com/alternayte/gx"

var RadioGroupItemFixtures = gx.Fixtures[RadioGroupItemProps]{
	"Unchecked": {Name: "item-unchecked", Value: "free", Label: "Free"},
	"Checked":   {Name: "item-checked", Value: "pro", Checked: true, Label: "Pro"},
	"Disabled":  {Name: "item-disabled", Value: "pro", Checked: true, Disabled: true, Label: "Pro"},
	"Invalid":   {Name: "item-invalid", Value: "pro", Invalid: true, Label: "Pro"},
}

The theme must define these tokens: --primary, --input, --ring, --destructive.

Usage

<radiogroup.RadioGroup label="Plan">
  <radiogroup.RadioGroupItem name="plan" value="free" checked={true} label="Free" />
  <radiogroup.RadioGroupItem name="plan" value="pro" label="Pro" />
</radiogroup.RadioGroup>

Each item is a native radio input behind a styled circle. Disabled and Invalid set the state of the input.

Examples

RadioGroup: Vertical

<radiogroup.RadioGroup label="Plan">
  <radiogroup.RadioGroupItem name="plan" value="free" checked label="Free" />
  <radiogroup.RadioGroupItem name="plan" value="pro" label="Pro" />
</radiogroup.RadioGroup>

RadioGroup: Horizontal

<radiogroup.RadioGroup label="Size" orientation={radiogroup.Horizontal}>
  <radiogroup.RadioGroupItem name="size" value="s" checked label="Small" />
  <radiogroup.RadioGroupItem name="size" value="m" label="Medium" />
</radiogroup.RadioGroup>

RadioGroupItem: Unchecked

<radiogroup.RadioGroupItem name="item-unchecked" value="free" label="Free" />

RadioGroupItem: Checked

<radiogroup.RadioGroupItem name="item-checked" value="pro" checked label="Pro" />

RadioGroupItem: Disabled

<radiogroup.RadioGroupItem name="item-disabled" value="pro" checked disabled label="Pro" />

RadioGroupItem: Invalid

<radiogroup.RadioGroupItem name="item-invalid" value="pro" invalid label="Pro" />

API reference

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

radiogroup.RadioGroup

Prop Type Default Description
Orientation Orientation Vertical Orientation sets the layout direction: Vertical or Horizontal.
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 radio items.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

radiogroup.RadioGroupItem

Prop Type Default Description
Name string "" Name is the form field name. Every item of one group has the same name.
Value string "" Value is the value the form submits when the item is checked.
Checked bool false Checked selects the item for the first render.
Disabled bool false Disabled disables the item.
Invalid bool false Invalid sets aria-invalid on the input and shows the error style.
Label string "" Label is the text beside the radio. An empty value renders no text.
Class string "" Class adds classes to the root element.
Children gx.Node nil Children is content beside the radio, after the label text.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

Do and do not

Do

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

Don't

  • Do not use a radio group for a boolean. Use a checkbox or a switch.
  • Do not mix two names in one group.

Keyboard

Key Action
Tab Moves focus to the checked item.
Arrow keys Moves the choice and selects it.
Space Selects the focused item.