Radio Group

Grouped selection primitive that coordinates ui-radio descendants, keyboard navigation, and normalized selection events.

Default horizontal

With orientation omitted, Radio Group lays out its options horizontally.

Options

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-radio-group/ui-radio-group.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-radio/ui-radio.html"
>

<ui-radio-group
  label="Options"
  name="radio-group-group"
>
  <ui-radio
    value="one"
  >One</ui-radio>
  <ui-radio
    value="two"
  >Two</ui-radio>
</ui-radio-group>
<script setup>
  import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
</script>

<template>
  <UiRadioGroup
    label="Options"
    name="radio-group-group"
  >
    <UiRadio
      value="one"
    >One</UiRadio>
    <UiRadio
      value="two"
    >Two</UiRadio>
  </UiRadioGroup>
</template>
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

export default function Example() {
  return <UiRadioGroup
    label="Options"
    name="radio-group-group"
  >
    <UiRadio
      value="one"
    >One</UiRadio>
    <UiRadio
      value="two"
    >Two</UiRadio>
  </UiRadioGroup>;
}
/** @jsxImportSource preact */
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

export default function Example() {
  return <UiRadioGroup
    label="Options"
    name="radio-group-group"
  >
    <UiRadio
      value="one"
    >One</UiRadio>
    <UiRadio
      value="two"
    >Two</UiRadio>
  </UiRadioGroup>;
}
<script>
  import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
</script>

<UiRadioGroup
  label="Options"
  name="radio-group-group"
>
  <UiRadio
    value="one"
  >One</UiRadio>
  <UiRadio
    value="two"
  >Two</UiRadio>
</UiRadioGroup>
import { Component } from "@angular/core";
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiRadioGroup, UiRadio],
  template: `
    <fieldset
      ui-radio-group
      [label]="'Options'"
      [name]="'radio-group-group'"
    >
      <label
        ui-radio
        [value]="'one'"
      >One</label>
      <label
        ui-radio
        [value]="'two'"
      >Two</label>
    </fieldset>
  `,
})
export default class Example {}

Vertical, required, and value

Orientation changes the axis; required and value define group validation and selection.

Priority

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-radio-group/ui-radio-group.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-radio/ui-radio.html"
>

<ui-radio-group
  label="Priority"
  value="two"
  name="radio-group-vertical"
  orientation="vertical"
  required
>
  <ui-radio
    value="one"
  >One</ui-radio>
  <ui-radio
    value="two"
  >Two</ui-radio>
  <ui-radio
    value="three"
  >Three</ui-radio>
</ui-radio-group>
<script setup>
  import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
</script>

<template>
  <UiRadioGroup
    label="Priority"
    value="two"
    name="radio-group-vertical"
    orientation="vertical"
    :required="true"
  >
    <UiRadio
      value="one"
    >One</UiRadio>
    <UiRadio
      value="two"
    >Two</UiRadio>
    <UiRadio
      value="three"
    >Three</UiRadio>
  </UiRadioGroup>
</template>
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

export default function Example() {
  return <UiRadioGroup
    label="Priority"
    value="two"
    name="radio-group-vertical"
    orientation="vertical"
    required
  >
    <UiRadio
      value="one"
    >One</UiRadio>
    <UiRadio
      value="two"
    >Two</UiRadio>
    <UiRadio
      value="three"
    >Three</UiRadio>
  </UiRadioGroup>;
}
/** @jsxImportSource preact */
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

export default function Example() {
  return <UiRadioGroup
    label="Priority"
    value="two"
    name="radio-group-vertical"
    orientation="vertical"
    required
  >
    <UiRadio
      value="one"
    >One</UiRadio>
    <UiRadio
      value="two"
    >Two</UiRadio>
    <UiRadio
      value="three"
    >Three</UiRadio>
  </UiRadioGroup>;
}
<script>
  import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
</script>

<UiRadioGroup
  label="Priority"
  value="two"
  name="radio-group-vertical"
  orientation="vertical"
  required
>
  <UiRadio
    value="one"
  >One</UiRadio>
  <UiRadio
    value="two"
  >Two</UiRadio>
  <UiRadio
    value="three"
  >Three</UiRadio>
</UiRadioGroup>
import { Component } from "@angular/core";
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiRadioGroup, UiRadio],
  template: `
    <fieldset
      ui-radio-group
      [label]="'Priority'"
      [value]="'two'"
      [name]="'radio-group-vertical'"
      [orientation]="'vertical'"
      [required]="true"
    >
      <label
        ui-radio
        [value]="'one'"
      >One</label>
      <label
        ui-radio
        [value]="'two'"
      >Two</label>
      <label
        ui-radio
        [value]="'three'"
      >Three</label>
    </fieldset>
  `,
})
export default class Example {}

disabled

Disabled makes every radio in the group unavailable.

Plan

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-radio-group/ui-radio-group.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-radio/ui-radio.html"
>

<ui-radio-group
  label="Plan"
  name="radio-group-disabled-plan"
  value="free"
  disabled
>
  <ui-radio
    value="free"
    checked
  >Free</ui-radio>
  <ui-radio
    value="pro"
  >Pro</ui-radio>
</ui-radio-group>
<script setup>
  import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
</script>

<template>
  <UiRadioGroup
    label="Plan"
    name="radio-group-disabled-plan"
    value="free"
    :disabled="true"
  >
    <UiRadio
      value="free"
      :checked="true"
    >Free</UiRadio>
    <UiRadio
      value="pro"
    >Pro</UiRadio>
  </UiRadioGroup>
</template>
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

export default function Example() {
  return <UiRadioGroup
    label="Plan"
    name="radio-group-disabled-plan"
    value="free"
    disabled
  >
    <UiRadio
      value="free"
      checked
    >Free</UiRadio>
    <UiRadio
      value="pro"
    >Pro</UiRadio>
  </UiRadioGroup>;
}
/** @jsxImportSource preact */
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

export default function Example() {
  return <UiRadioGroup
    label="Plan"
    name="radio-group-disabled-plan"
    value="free"
    disabled
  >
    <UiRadio
      value="free"
      checked
    >Free</UiRadio>
    <UiRadio
      value="pro"
    >Pro</UiRadio>
  </UiRadioGroup>;
}
<script>
  import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
</script>

<UiRadioGroup
  label="Plan"
  name="radio-group-disabled-plan"
  value="free"
  disabled
>
  <UiRadio
    value="free"
    checked
  >Free</UiRadio>
  <UiRadio
    value="pro"
  >Pro</UiRadio>
</UiRadioGroup>
import { Component } from "@angular/core";
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiRadioGroup, UiRadio],
  template: `
    <fieldset
      ui-radio-group
      [label]="'Plan'"
      [name]="'radio-group-disabled-plan'"
      [value]="'free'"
      [disabled]="true"
    >
      <label
        ui-radio
        [value]="'free'"
        [checked]="true"
      >Free</label>
      <label
        ui-radio
        [value]="'pro'"
      >Pro</label>
    </fieldset>
  `,
})
export default class Example {}

Invalid group

Invalid marks the whole group and its legend; a form can show a related validation message beside it.

Delivery method

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-radio-group/ui-radio-group.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-radio/ui-radio.html"
>

<ui-radio-group
  invalid
  required
  name="delivery"
  label="Delivery method"
>
  <ui-radio
    value="ship"
  >Ship</ui-radio>
  <ui-radio
    value="pickup"
  >Pick up</ui-radio>
</ui-radio-group>
<script setup>
  import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
</script>

<template>
  <UiRadioGroup
    :invalid="true"
    :required="true"
    name="delivery"
    label="Delivery method"
  >
    <UiRadio
      value="ship"
    >Ship</UiRadio>
    <UiRadio
      value="pickup"
    >Pick up</UiRadio>
  </UiRadioGroup>
</template>
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

export default function Example() {
  return <UiRadioGroup
    invalid
    required
    name="delivery"
    label="Delivery method"
  >
    <UiRadio
      value="ship"
    >Ship</UiRadio>
    <UiRadio
      value="pickup"
    >Pick up</UiRadio>
  </UiRadioGroup>;
}
/** @jsxImportSource preact */
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

export default function Example() {
  return <UiRadioGroup
    invalid
    required
    name="delivery"
    label="Delivery method"
  >
    <UiRadio
      value="ship"
    >Ship</UiRadio>
    <UiRadio
      value="pickup"
    >Pick up</UiRadio>
  </UiRadioGroup>;
}
<script>
  import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
</script>

<UiRadioGroup
  invalid
  required
  name="delivery"
  label="Delivery method"
>
  <UiRadio
    value="ship"
  >Ship</UiRadio>
  <UiRadio
    value="pickup"
  >Pick up</UiRadio>
</UiRadioGroup>
import { Component } from "@angular/core";
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiRadioGroup, UiRadio],
  template: `
    <fieldset
      ui-radio-group
      [invalid]="true"
      [required]="true"
      [name]="'delivery'"
      [label]="'Delivery method'"
    >
      <label
        ui-radio
        [value]="'ship'"
      >Ship</label>
      <label
        ui-radio
        [value]="'pickup'"
      >Pick up</label>
    </fieldset>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: fieldset

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
disabledDisables every radio in the group through the native fieldset, so no choice can be made and the group submits nothing with its form. A radio disabled on its own stays disabled when the group is enabled.disabledbooleanfalse—
formAssociate the group’s native radios with this form ID. Omit it to use their containing form; an empty string leaves them outside form submission.formstring——
invalidExposes an externally validated error on the group and styles its legend with the field danger colour.invalidbooleanfalse—
labelThe group's legend, shown above the radios and announced as the group's name. Phrase it as the question the radios answer.labelstringOptions—
label-visibilityvisible shows the legend; sr-only hides it visually but keeps it as the group's accessible name. Use sr-only only when the surrounding layout already makes the choice clear, as with a row of colour swatches.labelVisibilitykeywordvisiblevisible, sr-only
nameThe native name given to every radio in the group, under which a form submits the chosen value. Left empty, the group generates a unique name that keeps the radios together, so set it when the choice submits with a form.namestring—
orientationhorizontal lays the radios out in a row; vertical stacks them in a column. The arrow keys move between radios either way.orientationkeywordhorizontalhorizontal, vertical
requiredMakes a choice required, as a required native radio does for its group: the group states aria-required, every radio in it is marked required, and native form validation fails until one is checked.requiredbooleanfalse—
valueThe value of the checked radio, initially or when set externally; empty checks none. The user's choice updates it from there and is reported as select and change events, and a form reset returns the group to it.valuestring—

Framework props

NameTypeDefaultAllowed values
disabledbooleanfalse—
formstring——
invalidbooleanfalse—
labelstringOptions—
labelVisibility"visible" | "sr-only"visiblevisible, sr-only
namestring—
orientation"horizontal" | "vertical"horizontalhorizontal, vertical
requiredbooleanfalse—
valuestring—

Events

NameDetailDescription
changeobject
  • checked: boolean (required)
  • value: string (required)
  • trigger: keyword (required)
Emitted when a toggleable control changes checked state.
selectobject
  • value: string (required)
  • previousValue: string (required)
  • trigger: keyword (required)
Emitted when a selectable option becomes active.

Slots

NamePurpose
defaultDefault child content.

Design tokens

Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.

Component tokens

TokenDeclared valuesFallback
--ui-radio-group-invalid-text—var(--ui-field-danger, var(--ui-danger-solid))

Shared tokens

TokenFallback
--ui-danger-solid—
--ui-field-dangervar(--ui-danger-solid)
--ui-font-medium—
--ui-space-2—
--ui-text-primary—