Radio ​

Native radio input resolved through the shared styling contract. Group value is owned by createRadioGroupController in the DOM engine; React / Vue / CE expose radio items that you wire with a shared name and parent-controlled checked (or native HTML grouping). There is no React/Vue RadioGroup component in this beta.

Preview
Plan

Usage ​

tsx
import { useState } from "react";
import { Radio } from "@sometic/react/selection";

export function Example() {
    const [value, setValue] = useState("pro");
    return (
        <div role="radiogroup" aria-label="Plan">
            <Radio
                name="plan"
                value="free"
                checked={value === "free"}
                onValueChange={setValue}
                aria-label="Free"
            />
            <Radio
                name="plan"
                value="pro"
                checked={value === "pro"}
                onValueChange={setValue}
                aria-label="Pro"
            />
        </div>
    );
}
vue
<script setup>
import { ref } from "vue";
import { Radio } from "@sometic/vue/selection";

const value = ref("pro");
</script>

<template>
    <div role="radiogroup" aria-label="Plan">
        <Radio
            name="plan"
            value="free"
            :checked="value === 'free'"
            aria-label="Free"
            @value-change="value = $event"
        />
        <Radio
            name="plan"
            value="pro"
            :checked="value === 'pro'"
            aria-label="Pro"
            @value-change="value = $event"
        />
    </div>
</template>
js
import { createRadioGroupController } from "@sometic/dom/radio";

const group = createRadioGroupController({
    name: "plan",
    defaultValue: "pro",
    onValueChange(next) {
        console.log(next);
    },
});

for (const input of document.querySelectorAll('input[type="radio"][name="plan"]')) {
    const apply = () => {
        const view = group.resolveItem(input.value);
        input.checked = view.checked;
        for (const [key, attr] of Object.entries(view.attributes)) {
            input.setAttribute(key, attr);
        }
    };
    apply();
    input.addEventListener("change", () => {
        group.setValue(input.value);
        apply();
    });
}
html
<script type="module">
    import { registerSelectionElements } from "@sometic/elements/selection";
    registerSelectionElements();
</script>

<div role="radiogroup" aria-label="Plan">
    <sometic-radio name="plan" value="free" aria-label="Free"></sometic-radio>
    <sometic-radio name="plan" value="pro" checked aria-label="Pro"></sometic-radio>
</div>
html
<script src="https://cdn.jsdelivr.net/npm/@sometic/elements@1.1.6/dist/cdn/sometic-elements.iife.js"></script>

<div role="radiogroup" aria-label="Plan">
    <sometic-radio name="plan" value="free" aria-label="Free"></sometic-radio>
    <sometic-radio name="plan" value="pro" checked aria-label="Pro"></sometic-radio>
</div>
html
<script
    type="module"
    src="https://cdn.jsdelivr.net/npm/@sometic/elements@1.1.6/dist/cdn/sometic-elements.esm.js"
></script>

<div role="radiogroup" aria-label="Plan">
    <sometic-radio name="plan" value="free" aria-label="Free"></sometic-radio>
    <sometic-radio name="plan" value="pro" checked aria-label="Pro"></sometic-radio>
</div>

Vue ​

vue
<script setup>
import { ref } from "vue";
import { Radio } from "@sometic/vue/selection";

const value = ref("pro");
</script>

<template>
    <div role="radiogroup" aria-label="Plan">
        <Radio
            name="plan"
            value="free"
            :checked="value === 'free'"
            aria-label="Free"
            @value-change="value = $event"
        />
        <Radio
            name="plan"
            value="pro"
            :checked="value === 'pro'"
            aria-label="Pro"
            @value-change="value = $event"
        />
    </div>
</template>

Beta note: @sometic/react/selection and @sometic/vue/selection export Radio (with Checkbox / Switch / Select). Compose group state yourself, or use createRadioGroupController from @sometic/dom/radio for vanilla.

How it works ​

  1. Engine: resolveRadio requires value: string and builds native radio attrs. createRadioGroupController holds group string | null and resolveItem.
  2. Adapters: React/Vue Radio is a single item: required value, optional checked, onValueChange(value) / valueChange when selected.
  3. Custom element: sometic-radio observes checked, disabled, name, value, shadow.

Anatomy ​

Partdata-slotRole
Rootroot<input type="radio">

State: data-checked, data-disabled, data-invalid, optional size/variant.

Props / attributes ​

React RadioProps ​

PropTypeDefaultDescription
valuestringrequiredItem value
checkedbooleanfalseSelected
onValueChange(value: string) => voidFires with this item's value when chosen
disabled / required / invalidbooleanfalseFlags
namestringShared group name
size / variantstringTheme
stylingStyleableProps<"root">Unstyled
Native attrsremaining input attrsForwarded; ref OK

Vue ​

Props: value (required), checked, disabled, name. Emits: valueChange.

DOM group controller ​

createRadioGroupController({ value?, defaultValue?, name?, onValueChange? }) → resolveItem(itemValue), setValue.

Custom element (sometic-radio) ​

Observed: checked, disabled, name, value, shadow.

Events / callbacks ​

SurfaceEventPayload
ReactonValueChangeitem value string
VuevalueChangeitem value string
CEchange-style checked events
Group controlleronValueChangestring | null

Controlled vs uncontrolled ​

Items are typically controlled by parent group state. Native HTML grouping via shared name also works uncontrolled without React/Vue state (browser manages exclusivity).

Form participation ​

Native radios with shared name submit the selected value. Works with Form / sometic-form.

Accessibility ​

ConcernGuidance
Group nameWrap with role="radiogroup" + aria-label / aria-labelledby
Item labelsVisible labels per item or aria-label
KeyboardNative arrow-key group behavior when name is shared
Not listboxThis is radio, not Combobox / Menu
RequiredPrefer form-level validation that the group has a value

Styling ​

[data-checked], [data-disabled], [data-invalid], [data-slot="root"], optional size/variant.

Edge cases ​

  • Missing shared name: items will not native-group; manage checked yourself.
  • null group value: no item checked (controller path).
  • SSR: resolve is pure; register CE in the browser.
  • Inventing RadioGroup: do not assume a React RadioGroup export; compose state.
  • Select vs Radio: Select for longer / compact lists; Radio for visible concurrent options.
  • Multi-instance: independent items; exclusivity comes from name or parent state.

Performance notes ​

Per-item resolve is cheap. Prefer one parent state update over remounting the group. Import @sometic/react/selection subpaths for tree-shaking.

When to use / When not ​

Use for mutually exclusive choices in a short list.

Do not use for:

FAQ ​

Where is React RadioGroup? Not shipped. Compose with state + Radio items or DOM createRadioGroupController.

Why onValueChange passes the item value? So parents can set group state without reading the DOM.

Required on one item? Prefer form-level validation that the group has a value.

Select vs Radio? Select for longer option lists / compact UI; Radio for visible concurrent options.

CE grouping? Shared name attribute across sometic-radio nodes.

Does React forward refs? Yes, to the underlying <input>.

Arrow keys without shared name? Native grouping needs shared name; otherwise manage focus yourself.

Invalid? Sets aria-invalid / data-invalid; pair with Field for messages.

Bundle tip? Import @sometic/react/selection (or Vue / elements / dom matching subpaths).