Skip to main content

RadioGroup

Choose one value from a visible set of radio options.

Use RadioGroup for one choice from a small visible set. For a longer or space-constrained collection, use Select.

Basic usage#

import { Field, RadioGroup } from 'moraine'

export function Example() {
  return (
    <Field label="Visibility">
      <RadioGroup
        items={[
          { value: 'public', label: 'Public' },
          { value: 'private', label: 'Private' },
        ]}
      />
    </Field>
  )
}

Playground#

Props
Slots

Anatomy#

RadioGroup [component; slot=root]
└── item [slot]
    ├── container [slot]
    │   ├── input [internal]
    │   └── control [slot]
    │       └── indicator [slot]
    └── wrapper [slot]
        ├── label [slot]
        └── description [slot]

Usage#

Single selection#

Use one string value for the selected item and pair value with onValueChange for controlled state. Disabled items remain visible but cannot be selected. The group uses radio keyboard behavior rather than the independent focus order of checkboxes.

import { RadioGroup } from 'moraine'
import { createSignal } from 'solid-js'

const PLAN_OPTIONS = [
  { label: 'Free tier', value: 'free', description: 'For personal projects with basic features.' },
  {
    label: 'Pro tier',
    value: 'pro',
    description: 'Advanced collaboration and unlimited bandwidth.',
  },
  { label: 'Enterprise', value: 'enterprise', description: 'Dedicated support and custom SLA.' },
]

export function Selection() {
  const [plan, setPlan] = createSignal('pro')

  return (
    <div class="max-w-md w-full space-y-3">
      <RadioGroup items={PLAN_OPTIONS} value={plan()} onValueChange={setPlan} />
      <p class="text-xs text-muted-foreground">
        Active plan: <span class="text-foreground font-medium">{plan()}</span>
      </p>
    </div>
  )
}

Forms#

Set name for native form submission. For schema binding and validation, place the group inside form.Field from createForm. Standalone Field supplies labels and messages without binding a form store.

import { RadioGroup } from 'moraine'

const ENV_OPTIONS = [
  { label: 'Development', value: 'dev' },
  { label: 'Staging', value: 'staging' },
  { label: 'Production', value: 'prod' },
]

export function FormUsage() {
  return (
    <div class="max-w-md w-full">
      <RadioGroup name="environment" items={ENV_OPTIONS} defaultValue="staging" />
    </div>
  )
}

Keyboard interaction#

Key Description
↓ / → Moves focus and selection to the next enabled radio item.
↑ / ← Moves focus and selection to the previous enabled radio item.
Space Selects the focused radio item.
⇥ Enters or leaves the group according to normal focus order.

Examples#

Controlled value and disabled items#

import { RadioGroup } from 'moraine'
import { createSignal } from 'solid-js'

export function ControlledDisabled() {
  const [value, setValue] = createSignal('pro')

  return (
    <div class="max-w-xl space-y-3">
      <RadioGroup
        items={[
          {
            value: 'starter',
            label: 'Starter (Legacy)',
            description: 'For personal projects',
            disabled: true,
          },
          { value: 'pro', label: 'Pro', description: 'For teams and scaling' },
          { value: 'enterprise', label: 'Enterprise', description: 'For regulated workloads' },
        ]}
        value={value()}
        onValueChange={setValue}
        variant="table"
        orientation="horizontal"
      />
      <p class="text-xs text-muted-foreground">Current plan: {value()}</p>
    </div>
  )
}

Indicator position#

import { RadioGroup } from 'moraine'
import type { RadioGroupT } from 'moraine'
import { For } from 'solid-js'

export function IndicatorPositions() {
  const ITEMS = [
    { value: 'starter', label: 'Starter', description: 'For personal projects' },
    { value: 'pro', label: 'Pro', description: 'For teams and scaling' },
    { value: 'enterprise', label: 'Enterprise', description: 'For regulated workloads' },
  ]

  const INDICATORS: RadioGroupIndicatorName[] = ['start', 'end', 'hidden']

  type RadioGroupIndicatorName = Exclude<RadioGroupT.Variant['indicator'], undefined>

  return (
    <div class="gap-4 grid lg:grid-cols-3 sm:grid-cols-2">
      <For each={INDICATORS}>
        {(indicator) => (
          <div class="p-4 b-1 b-border rounded-lg space-y-2">
            <p class="text-sm font-medium">Indicator: {indicator}</p>
            <RadioGroup items={ITEMS} variant="card" indicator={indicator} defaultValue="pro" />
          </div>
        )}
      </For>
    </div>
  )
}

Form integration#

import { Button, createForm, RadioGroup } from 'moraine'
import { createSignal } from 'solid-js'
import * as v from 'valibot'

const ENV_OPTIONS = [
  { label: 'Development', value: 'dev' },
  { label: 'Staging', value: 'staging' },
  { label: 'Production', value: 'prod' },
]

export function FormIntegration() {
  const [submittedEnv, setSubmittedEnv] = createSignal('staging')
  const form = createForm({
    schema: v.object({
      environment: v.pipe(v.string(), v.nonEmpty('Please select a target environment.')),
    }),
    initialInput: { environment: 'staging' },
    validate: 'input',
  })

  return (
    <form.Form onSubmit={(output) => setSubmittedEnv(output.environment)}>
      <div class="max-w-xl space-y-4">
        <form.Field
          name="environment"
          label="Target Deployment Environment"
          description="Determines runtime configuration and secrets applied."
          required
        >
          <RadioGroup items={ENV_OPTIONS} />
        </form.Field>
        <div class="flex gap-3 items-center">
          <Button type="submit" variant="secondary" size="sm">
            Validate
          </Button>
          <p class="text-xs text-muted-foreground">Target: {submittedEnv()}</p>
        </div>
      </div>
    </form.Form>
  )
}

Attributes#

Attributes
data-disabledSlot: radio-group, radio-group-item, radio-group-controlDescription: Present when the component, slot, or item is disabled.
data-invalidSlot: radio-group, radio-group-controlDescription: Present when the field or form has a validation error.
data-readonlySlot: radio-group, radio-group-controlDescription: Present when the field is in read-only mode.
data-requiredSlot: radio-group, radio-group-controlDescription: Present when the field input is required.
data-checkedSlot: radio-group-item, radio-group-control, radio-group-indicatorDescription: Present when the item or toggle is checked.

Props#

Props for the RadioGroup component. Renders a <div> element by default.

Prop