Radio
Radio control primitive for mutually exclusive options, typically composed within ui-radio-group.
Default
With checked omitted, Radio starts unselected and enabled.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-radio/ui-radio.html"
>
<ui-radio
value="option"
name="radio-group"
>Radio</ui-radio><script setup>
import { UiRadio } from "@threadlabs/looma";
</script>
<template>
<UiRadio
value="option"
name="radio-group"
>Radio</UiRadio>
</template>import { UiRadio } from "@threadlabs/looma";
export default function Example() {
return <UiRadio
value="option"
name="radio-group"
>Radio</UiRadio>;
}/** @jsxImportSource preact */
import { UiRadio } from "@threadlabs/looma";
export default function Example() {
return <UiRadio
value="option"
name="radio-group"
>Radio</UiRadio>;
}<script>
import { UiRadio } from "@threadlabs/looma";
</script>
<UiRadio
value="option"
name="radio-group"
>Radio</UiRadio>import { Component } from "@angular/core";
import { UiRadio } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiRadio],
template: `
<label
ui-radio
[value]="'option'"
[name]="'radio-group'"
>Radio</label>
`,
})
export default class Example {}checked and disabled
Default-checked supplies initial selection while disabled prevents later changes.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-radio/ui-radio.html"
>
<ui-cluster
gap="l"
>
<ui-radio
name="radio-state"
value="checked"
checked
>Checked</ui-radio>
<ui-radio
name="radio-state"
value="disabled"
disabled
>Disabled</ui-radio>
</ui-cluster><script setup>
import { UiCluster, UiRadio } from "@threadlabs/looma";
</script>
<template>
<UiCluster
gap="l"
>
<UiRadio
name="radio-state"
value="checked"
:checked="true"
>Checked</UiRadio>
<UiRadio
name="radio-state"
value="disabled"
:disabled="true"
>Disabled</UiRadio>
</UiCluster>
</template>import { UiCluster, UiRadio } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
gap="l"
>
<UiRadio
name="radio-state"
value="checked"
checked
>Checked</UiRadio>
<UiRadio
name="radio-state"
value="disabled"
disabled
>Disabled</UiRadio>
</UiCluster>;
}/** @jsxImportSource preact */
import { UiCluster, UiRadio } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
gap="l"
>
<UiRadio
name="radio-state"
value="checked"
checked
>Checked</UiRadio>
<UiRadio
name="radio-state"
value="disabled"
disabled
>Disabled</UiRadio>
</UiCluster>;
}<script>
import { UiCluster, UiRadio } from "@threadlabs/looma";
</script>
<UiCluster
gap="l"
>
<UiRadio
name="radio-state"
value="checked"
checked
>Checked</UiRadio>
<UiRadio
name="radio-state"
value="disabled"
disabled
>Disabled</UiRadio>
</UiCluster>import { Component } from "@angular/core";
import { UiCluster, UiRadio } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCluster, UiRadio],
template: `
<div
ui-cluster
[gap]="'l'"
>
<label
ui-radio
[name]="'radio-state'"
[value]="'checked'"
[checked]="true"
>Checked</label>
<label
ui-radio
[name]="'radio-state'"
[value]="'disabled'"
[disabled]="true"
>Disabled</label>
</div>
`,
})
export default class Example {}With a description
The description slot says what a choice means, on a line under its label. It is the input's accessible description, not part of its name.
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="Who can open it"
name="radio-description"
orientation="vertical"
value="open"
>
<ui-radio
value="open"
>
Open
<span
slot="description"
>Everyone on the site can open it, with the role they have on the site.</span>
</ui-radio>
<ui-radio
value="private"
>
Private
<span
slot="description"
>Only the people you add can open it.</span>
</ui-radio>
</ui-radio-group><script setup>
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
</script>
<template>
<UiRadioGroup
label="Who can open it"
name="radio-description"
orientation="vertical"
value="open"
>
<UiRadio
value="open"
>Open<template #description>
<span>Everyone on the site can open it, with the role they have on the site.</span>
</template></UiRadio>
<UiRadio
value="private"
>Private<template #description>
<span>Only the people you add can open it.</span>
</template></UiRadio>
</UiRadioGroup>
</template>import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
export default function Example() {
return <UiRadioGroup
label="Who can open it"
name="radio-description"
orientation="vertical"
value="open"
>
<UiRadio
value="open"
slots={{
description: <span>Everyone on the site can open it, with the role they have on the site.</span>,
}}
>Open</UiRadio>
<UiRadio
value="private"
slots={{
description: <span>Only the people you add can open it.</span>,
}}
>Private</UiRadio>
</UiRadioGroup>;
}/** @jsxImportSource preact */
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
export default function Example() {
return <UiRadioGroup
label="Who can open it"
name="radio-description"
orientation="vertical"
value="open"
>
<UiRadio
value="open"
slots={{
description: <span>Everyone on the site can open it, with the role they have on the site.</span>,
}}
>Open</UiRadio>
<UiRadio
value="private"
slots={{
description: <span>Only the people you add can open it.</span>,
}}
>Private</UiRadio>
</UiRadioGroup>;
}<script>
import { UiRadio, UiRadioGroup } from "@threadlabs/looma";
</script>
{#snippet description()}
<span>Everyone on the site can open it, with the role they have on the site.</span>
{/snippet}
{#snippet radioDescription()}
<span>Only the people you add can open it.</span>
{/snippet}
<UiRadioGroup
label="Who can open it"
name="radio-description"
orientation="vertical"
value="open"
>
<UiRadio
value="open"
slots={{ description }}
>Open</UiRadio>
<UiRadio
value="private"
slots={{ description: radioDescription }}
>Private</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]="'Who can open it'"
[name]="'radio-description'"
[orientation]="'vertical'"
[value]="'open'"
>
<label
ui-radio
[value]="'open'"
>Open<ng-container ngProjectAs="[slot='description']">
<span>Everyone on the site can open it, with the role they have on the site.</span>
</ng-container></label>
<label
ui-radio
[value]="'private'"
>Private<ng-container ngProjectAs="[slot='description']">
<span>Only the people you add can open it.</span>
</ng-container></label>
</fieldset>
`,
})
export default class Example {}size
size="sm" keeps the radio's box and centres its label in the small control height, in the small controls' type size, so it lines up with a small button.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-radio/ui-radio.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<ui-cluster
gap="s"
align="center"
>
<ui-radio
size="sm"
>Radio</ui-radio>
<ui-button
size="sm"
>Small button</ui-button>
</ui-cluster><script setup>
import { UiButton, UiCluster, UiRadio } from "@threadlabs/looma";
</script>
<template>
<UiCluster
gap="s"
align="center"
>
<UiRadio
size="sm"
>Radio</UiRadio>
<UiButton
size="sm"
>Small button</UiButton>
</UiCluster>
</template>import { UiButton, UiCluster, UiRadio } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
gap="s"
align="center"
>
<UiRadio
size="sm"
>Radio</UiRadio>
<UiButton
size="sm"
>Small button</UiButton>
</UiCluster>;
}/** @jsxImportSource preact */
import { UiButton, UiCluster, UiRadio } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
gap="s"
align="center"
>
<UiRadio
size="sm"
>Radio</UiRadio>
<UiButton
size="sm"
>Small button</UiButton>
</UiCluster>;
}<script>
import { UiButton, UiCluster, UiRadio } from "@threadlabs/looma";
</script>
<UiCluster
gap="s"
align="center"
>
<UiRadio
size="sm"
>Radio</UiRadio>
<UiButton
size="sm"
>Small button</UiButton>
</UiCluster>import { Component } from "@angular/core";
import { UiButton, UiCluster, UiRadio } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCluster, UiRadio, UiButton],
template: `
<div
ui-cluster
[gap]="'s'"
[align]="'center'"
>
<label
ui-radio
[size]="'sm'"
>Radio</label>
<ng-template
ui-button
[size]="'sm'"
>Small button</ng-template>
</div>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: label
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
checked | Whether the radio is checked initially or when set externally. A form reset returns it to this. Inside a ui-radio-group the group's value decides instead. | checked | boolean | false | — |
disabled | Disables the native radio, so it cannot be chosen, focused, or reached with the arrow keys. | disabled | boolean | false | — |
name | The native name that groups radios: of the radios that share a name, only one can be checked, and the arrow keys move between them. Inside a ui-radio-group the group sets the name, so leave this empty there. | name | string | | — |
required | Marks the native radio required, so native form validation fails until a radio with the same name is checked. | required | boolean | false | — |
size | Aligns the radio with fields and buttons of the same size. It keeps its box size; the label takes that size's field type size and its first line centres in that control height (sm 2rem, lg 3rem by default), so in a row of inputs, selects, and buttons of one size it shares their height and their text baseline. Touch input keeps the same visible alignment. md is the standard layout. | size | keyword | md | sm, md, lg |
value | The value this radio stands for: submitted with a form, reported in its change event, and matched against a ui-radio-group's value to decide which radio is checked. | value | string | on | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
checked | boolean | false | — |
disabled | boolean | false | — |
name | string | | — |
required | boolean | false | — |
size | "sm" | "md" | "lg" | md | sm, md, lg |
value | string | on | — |
Events
| Name | Detail | Description |
|---|---|---|
change | object
| Emitted when a toggleable control changes checked state. |
Slots
| Name | Purpose |
|---|---|
default | Default child content. |
description | Named content for the description region. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
| Token | Declared values | Fallback |
|---|---|---|
--ui-radio-gap | — | var(--ui-space-2) |
--ui-radio-size | — | var(--ui-choice-mark-size) | 1.125rem |
Shared tokens
| Token | Fallback |
|---|---|
--ui-accent-solid | — |
--ui-border-strong | — |
--ui-border-width | — |
--ui-choice-hover-surface | — |
--ui-choice-mark-size | — |
--ui-control-border | var(--ui-border-strong) |
--ui-control-border-hover | var(--ui-border-strong) |
--ui-control-inset | — |
--ui-control-size-lg | — |
--ui-control-size-sm | — |
--ui-control-surface | var(--ui-surface-default) |
--ui-disabled-border | — |
--ui-disabled-surface | — |
--ui-disabled-text | — |
--ui-focus-ring | var(--ui-accent-solid) |
--ui-focus-width | — |
--ui-font-size-md | — |
--ui-font-size-sm | — |
--ui-line-height | — |
--ui-line-height-md | — |
--ui-motion-ease | — |
--ui-motion-fast | — |
--ui-motion-feedback | — |
--ui-motion-feedback-ease | — |
--ui-neutral-wash-hover | — |
--ui-radius-round | — |
--ui-space-1 | — |
--ui-space-2 | — |
--ui-surface-default | — |
--ui-surface-highlight | none | linear-gradient(transparent, transparent) |
--ui-text-on-accent | — |
--ui-text-primary | — |
--ui-text-secondary | — |