j3w1 / Radio groupVue · HTML · React · Astro

J3W1 / DESIGN SYSTEM

Radio group

A fieldset of native radios for one exclusive choice; each radio is a 16px square that fills with the primary action colour and an inner square when checked.

stable · LIGHT DOM · 1.1.0

Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.

Live variants

default

Default layout required

✕ Choose a layout.

<j3w1-radio-group id="example-radio-group-default"><fieldset class="radio-group" aria-describedby="example-radio-group-default-rg-layout-message">
  <legend class="radio-group-legend">Default layout <span class="radio-group-required" aria-hidden="true">*</span><span class="radio-group-required sr-only"> required</span></legend>
  <div class="radio-group-options">
    <label class="radio-group-option">
      <span class="radio-group-control">
        <input class="radio-group-input" type="radio" name="example-radio-group-default-layout" value="split">
        <span class="radio-group-dot" aria-hidden="true"></span>
      </span>
      <span class="radio-group-text">Split horizontally</span>
    </label>
    <label class="radio-group-option">
      <span class="radio-group-control">
        <input class="radio-group-input" type="radio" name="example-radio-group-default-layout" value="stacked">
        <span class="radio-group-dot" aria-hidden="true"></span>
      </span>
      <span class="radio-group-text">Stacked</span>
    </label>
    <label class="radio-group-option">
      <span class="radio-group-control">
        <input class="radio-group-input" type="radio" name="example-radio-group-default-layout" value="tabbed">
        <span class="radio-group-dot" aria-hidden="true"></span>
      </span>
      <span class="radio-group-text">Tabbed</span>
    </label>
  </div>
  <p class="radio-group-message" id="example-radio-group-default-rg-layout-message">✕ Choose a layout.</p>
</fieldset></j3w1-radio-group>

horizontal

Density required

✕ Choose a density.

<j3w1-radio-group id="example-radio-group-horizontal"><fieldset class="radio-group radio-group-horizontal" aria-describedby="example-radio-group-horizontal-rg-density-message">
  <legend class="radio-group-legend">Density <span class="radio-group-required" aria-hidden="true">*</span><span class="radio-group-required sr-only"> required</span></legend>
  <div class="radio-group-options">
    <label class="radio-group-option">
      <span class="radio-group-control">
        <input class="radio-group-input" type="radio" name="example-radio-group-horizontal-density" value="compact">
        <span class="radio-group-dot" aria-hidden="true"></span>
      </span>
      <span class="radio-group-text">Compact</span>
    </label>
    <label class="radio-group-option">
      <span class="radio-group-control">
        <input class="radio-group-input" type="radio" name="example-radio-group-horizontal-density" value="comfortable">
        <span class="radio-group-dot" aria-hidden="true"></span>
      </span>
      <span class="radio-group-text">Comfortable</span>
    </label>
  </div>
  <p class="radio-group-message" id="example-radio-group-horizontal-rg-density-message">✕ Choose a density.</p>
</fieldset></j3w1-radio-group>

Install and register

Use the exact version. If npm publication is pending, install the release tarball from the installation guide.

npm install --save-exact @j3w1/ui@1.1.0
import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/radio-group.css';
import '@j3w1/ui/register/radio-group';

Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.

Vue SFC, React, Astro and plain HTML integration →

Copy the complete implementation

npx --package=@j3w1/ui@1.1.0 j3w1-ui copy radio-group --out ./vendor/j3w1/radio-group

The CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.

Public API

attributes

NameType / signatureMeaning
disabledCustomEventDisable the native controls owned by this instance.
loadingCustomEventMark the primary control busy and prevent duplicate activation.

properties

NameType / signatureMeaning
disabledbooleanDelegates disabled state to native children.
namestringNative control name; application form keys remain explicit.

methods

NameType / signatureMeaning
refresh(): voidReconnect behavior after replacing native child structure.
focus(options?: FocusOptions): voidFocus the first usable native control.

events

NameType / signatureMeaning

Dependencies and verification

Registered components: none. Style dependencies: none.

Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.

Full implementation manifest, API and token dependency closure

Canonical specification

Purpose

One choice out of a few visible options. Below two options a checkbox or switch is used; above roughly six, a select. The radios are square, as every control in this theme is, and are told apart from checkboxes by the inner square instead of the ✓ glyph and by arrow-key movement instead of separate tab stops.

Anatomy

A <fieldset> whose <legend> asks the question; a list of <label> rows each holding the native input, drawn as a 16px square with appearance: none, and its text; the validation message at the end. The inner square is a real element stacked over the input in the same cell.

States

StateVisualNon-colour channel
default16px square, 1px color.border.control on color.surface.input; text color.text.default
hoverbox background → color.interaction.hover.bg; a checked box → color.action.primary.hover-bgcursor: pointer; hover-capable pointers only
focus-visiblering 1px dashed color.interaction.focus.ring at −2px around the boxthe ring
checkedbox fills color.action.primary.bg; an 8px inner square in color.action.primary.text; border stays color.border.controlthe inner square; checked
checked+focus-visiblethe fill and a ring in color.interaction.focus.ring-containerinner square and ring
disabledboxes color.interaction.disabled.bg with 1px color.border.disabled; inner square and text color.text.disableddisabled; cursor: not-allowed
invalidevery box border 2px color.status.danger.border; message with the ✕ glyphborder width 1 → 2px; glyph; aria-invalid
required* after the legend in color.status.danger.text plus visually hidden “required”required; the mark

Precedence: disabled > invalid > checked > hover; focus-visible is always drawn.

Keyboard

Native. The group is one tab stop; the arrow keys move both focus and the check, so moving through a group changes the value. That is why a radio group never triggers navigation or a request on change; the value is read on submit or through an explicit action.

Accessibility

The legend is read before each option and is the group’s name. Options share a name. The message is linked with aria-describedby on the fieldset and aria-invalid is set on the inputs after submit. A group with no default checked radio must have a required mark or a “none” option, so the empty state is intentional. Contrast: text 8.43:1, inner square 9.28:1 on the fill, box edge 4.45:1 on the input surface and 4.33:1 against the panel when checked, ring 4.69:1, container ring 5.57:1.

Portability

Square, border, fill and one inner square; no pseudo-element is needed. Hosts that insist on a circular radio indicator record the deviation rather than replacing the control with a custom widget.

Non-examples

Circular radios. A checked state shown by border colour alone with no inner square. Radios that submit or navigate on change. A group with no legend or with the legend rendered as a label on one option. Separate tab stops per radio. One radio on its own. Disabling by opacity. A div with role="radiogroup" built from spans.

j3w1 / command

  • SPEC Overview
  • SPEC Foundations
  • SPEC Components
  • SPEC Patterns
  • SPEC Tokens
  • SPEC Tools
  • SPEC Implement
  • SPEC For agents
  • SPEC Ports
  • SPEC Releases
  • DEMO Vue application demo
  • SPEC Complete UI theme specification