j3w1 / theme

j3w1 / theme4.0.0

j3w1 / command

  • SPEC Overview
  • SPEC Foundations
  • SPEC Components
  • SPEC Patterns
  • SPEC Tokens
  • SPEC Tools
  • SPEC Workbench
  • SPEC Form builder
  • SPEC Task kit
  • SPEC Recipes
  • SPEC Figma bridge
  • SPEC Report an issue
  • SPEC Implement
  • SPEC For agents
  • SPEC Ports
  • SPEC Releases
  • SPEC Specification
  • DEMO Vue application demo

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 · 4.0.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.

Markup
<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.

Markup
<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@4.0.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@4.0.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

Radio group radio-group

  • Family: Basic forms
  • Maturity: stable
  • Priority: R1
  • Since: 0.1.0
  • Native control: native <input type="radio"> inside <fieldset> with <legend> — APG pattern
  • specified
  • demonstrated
  • test implemented

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.

Report an issue with this component

Tokens

Focus a role to inspect its values; follow its link for persistent usage and source details.

PartRoleValueUse
box.bgcolor.surface.inputapproveduse#000000Form-control fill.
box.bordercolor.border.controlapproveduse#c81a1aD-041 owner correction: red control and floating-frame borders, distinct from rose text.
box.bg-hovercolor.interaction.hover.bgapproveduse#1c1010D-041 interaction.hover.bg: color.primitive.modern.hover.
box.ringcolor.interaction.focus.ringobserveduse#e539351px dashed focus ring on controls and rows.
box.bg-checkedcolor.action.primary.bgapproveduse#7d1310Primary button fill; D-026 darkened it off color12, which stays fixed as heritage ANSI 12.
box.bg-checked-hovercolor.action.primary.hover-bgobserveduse#911410Primary button hover; pair with action.primary.text (D-033).
box.dotcolor.action.primary.textapproveduse#ffa2a7Primary action labels: bright rose across rest, hover and pressed states (D-033).
box.ring-checkedcolor.interaction.focus.ring-containerobserveduse#ffa2a7Focus colour for containers and explicitly mapped filled controls (D-015); component rules determine ring geometry.
box.border-invalidcolor.status.danger.borderobserveduse#e53935Danger border, 2px on invalid inputs.
box.bg-disabledcolor.interaction.disabled.bgapproveduse#0d0707D-041 interaction.disabled.bg: color.primitive.modern.panel.
box.border-disabledcolor.border.disabledapproveduse#482b2bD-041 border.disabled: color.primitive.modern.divider.
box.dot-disabledcolor.text.disabledapproveduse#a3676bD-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills.
text.defaultcolor.text.defaultapproveduse#e99499D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills.
text.disabledcolor.text.disabledapproveduse#a3676bD-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills.
legend.textcolor.text.brightapproveduse#ffa2a7D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills.
legend.required-markcolor.status.danger.textobserveduse#f07178D-041 readable error text on neutral surfaces and native warning tints.
message.textcolor.status.danger.textobserveduse#f07178D-041 readable error text on neutral surfaces and native warning tints.

State matrix

Every declared state and combination, statically rendered; the default cell is live. Sizes: compact, comfortable (switch with the density control above).

StateVerticalHorizontal
default
Default layout required

✕ Choose a layout.

Density required

✕ Choose a density.

hover
focus-visible
checked
checked+focus-visible
disabled
invalid
required

Purpose

One choice among two to roughly six visible options; fewer is a checkbox or switch, more a select. It differs from a checkbox by the inner square instead of ✓ and by arrow keys instead of separate tab stops.

Anatomy

Parts as listed in the anatomy.

States

StateVisualNon-colour channel
default16px square, 1px color.border.control on color.surface.input; text color.text.default—
hoverbox → color.interaction.hover.bg; a checked box → color.action.primary.hover-bgcursor: pointer; hover-capable pointers only
focus-visible1px dashed color.interaction.focus.ring ring at −2px around the boxthe ring
checkedbox fills color.action.primary.bg; 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, 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 and one tab stop. Arrows move focus and the check together, so a radio group never navigates or sends a request on change; the value is read on submit or through an explicit action.

Accessibility

The legend names the group and is read before each option. aria-invalid is set on the inputs after submit. A group with no default checked radio has a required mark or a “none” option, so the empty state is intentional. Text meets 4.5:1; box edges, the inner square and rings 3:1.

Portability

Square, border, fill and one inner square; no pseudo-element. A host that insists on a circular indicator records the deviation instead of replacing the control with a custom widget.

Non-examples

Circular radios. Checked shown by border colour alone, without the inner square. Submitting or navigating on change. No legend, or the legend as one option’s label. A tab stop per radio. A lone radio. Disabling by opacity. A div with role="radiogroup" built from spans.

Contrast report

17 declared pairs measured on resolved sRGB values with the WCAG 2.x formula; 0 waived (decorative only), 0 failing. A pass is a design target for the named role, not an application-wide conformance claim.

PairStateKindForegroundBackgroundRatioMinResult
radio-group default borderdefaultuicolor.border.control #c81a1acolor.surface.input #0000003.623pass
radio-group hover borderhoveruicolor.border.control #c81a1acolor.interaction.hover.bg #1c10103.203pass
radio-group focus-visible borderfocus-visibleuicolor.border.control #c81a1acolor.surface.input #0000003.623pass
radio-group focus-visible outlinefocus-visibleuicolor.interaction.focus.ring #e53935color.surface.input #0000004.973pass
radio-group checked textcheckedtextcolor.action.primary.text #ffa2a7color.action.primary.bg #7d13105.574.5pass
radio-group checked+focus-visible textchecked+focus-visibletextcolor.action.primary.text #ffa2a7color.action.primary.bg #7d13105.574.5pass
radio-group checked+focus-visible outlinechecked+focus-visibleuicolor.interaction.focus.ring-container #ffa2a7color.action.primary.bg #7d13105.573pass
radio-group invalid borderinvaliduicolor.status.danger.border #e53935color.surface.input #0000004.973pass
radio-group required borderrequireduicolor.border.control #c81a1acolor.surface.input #0000003.623pass
option text on the panel surface—textcolor.text.default #e99499color.surface.default #0d07078.724.5pass
legend on the panel surface—textcolor.text.bright #ffa2a7color.surface.default #0d070710.454.5pass
checked radio edge against the panel (the border stays border.control)checkeduicolor.border.control #c81a1acolor.surface.default #0d07073.443pass
inner square on the hovered checked fillhoveruicolor.action.primary.text #ffa2a7color.action.primary.hover-bg #9114104.753pass
validation message on the panel surface—textcolor.status.danger.text #f07178color.surface.default #0d07076.984.5pass
disabled option text (exempt; house floor 3:1)disableduicolor.text.disabled #a3676bcolor.surface.default #0d07074.483pass
disabled inner square (exempt; house floor 3:1)disableduicolor.text.disabled #a3676bcolor.interaction.disabled.bg #0d07074.483pass
disabled border (exempt)disableduicolor.border.disabled #482b2bcolor.interaction.disabled.bg #0d07071.581pass

Keys

KeyAction
Tab / Shift+TabEnters the group on the checked radio, or the first when none is checked; leaves it entirely.
Up / Down / Left / RightMoves the check to the previous or next radio and selects it (native); wraps at the ends.
SpaceChecks the focused radio when none is checked.

For agents

Component JSON ·Implementation brief ·Build a task kit ·

Related: checkbox switch segmented-control fieldset field select

Stress fixtures: FX-LONG FX-320 FX-ZOOM-200 FX-I18N FX-RTL FX-RM FX-HC FX-TOUCH FX-DENSITY FX-STATE-MATRIX