PixelRadioGroup

Single-select grouped radios with a pixel dot indicator, fieldset/legend semantics, and tone + surface variants.

  • Controlled — value + onChange (React), v-model (Vue), [(value)] or forms (Angular) — over a list of options.
  • Renders as a real <fieldset> with role="radiogroup" and a <legend> from label.
  • Seven tones and pixel/linear surfaces share the kit-wide design tokens.
  • Optional name emits a hidden input so it serializes inside native forms.
  • disabled cascades to every option button (not just visually).
Status
stable
Category
forms
Since
v1.0.0

API

import { PixelRadioGroup } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
label (required)string—Legend rendered above the group.
value (required)string—Currently-selected option value.
options (required)Option[]—Radio items.
onChange (required)(next: string) => void—Fires with the new value when the user picks a radio.
disabledbooleanfalseDisables every radio in the group.
tone'green' | 'cyan' | 'gold' | 'red' | 'purple' | 'pink' | 'neutral''cyan'Visual tone for the selected radio. Default: 'cyan'.
surface'pixel' | 'linear'—Surface variant. Inherits from PxlKitSurfaceProvider when omitted.
namestring—Form-serialization name.
requiredboolean—Marks the field as required for native form validation.

ref points to its element (HTMLFieldSetElement).

Accessibility

WCAG target: 2.1 AA

  • radiogroup

Wrapped in a <fieldset role="radiogroup"> with <legend> derived from label and aria-disabled / aria-required mirroring the props. Each option is a <button role="radio"> with aria-checked reflecting selection. When name is set a hidden <input> mirrors the current value so the group participates in native <form> submissions. Keyboard focus shows on the focused radio's indicator: a ring in the tone on the linear surface, the indicator's edge on the pixel surface, whose cut corners would clip a ring.

Keyboard

KeyActionWhen
TabMoves focus into the radio group and on to the next focusable element after the last option.—
SpaceActivates the focused radio option (native <button> behavior).—
EnterActivates the focused radio option (native <button> behavior).—

Usage

import { useState } from 'react';
import { PixelRadioGroup } from '@pxlkit/ui-kit';

const PLANS = [
  { value: 'free', label: 'Free' },
  { value: 'pro', label: 'Pro' },
  { value: 'team', label: 'Team' },
];

export function Default() {
  const [value, setValue] = useState('free');
  return (
    <PixelRadioGroup
      label="Plan"
      value={value}
      options={PLANS}
      onChange={setValue}
    />
  );
}

Examples

Default

import { useState } from 'react';
import { PixelRadioGroup } from '@pxlkit/ui-kit';

const PLANS = [
  { value: 'free', label: 'Free' },
  { value: 'pro', label: 'Pro' },
  { value: 'team', label: 'Team' },
];

export function Default() {
  const [value, setValue] = useState('free');
  return (
    <PixelRadioGroup
      label="Plan"
      value={value}
      options={PLANS}
      onChange={setValue}
    />
  );
}

Controlled

import { useState } from 'react';
import { PixelRadioGroup } from '@pxlkit/ui-kit';

const PLANS = [
  { value: 'free', label: 'Free' },
  { value: 'pro', label: 'Pro' },
  { value: 'team', label: 'Team' },
];

export function Controlled() {
  const [value, setValue] = useState('pro');
  return (
    <div className="space-y-2">
      <PixelRadioGroup
        label="Selected plan"
        value={value}
        options={PLANS}
        onChange={setValue}
        tone="cyan"
      />
      <p className="text-xs text-retro-muted">Picked: {value}</p>
    </div>
  );
}

Tones

import { useState } from 'react';
import { PixelRadioGroup } from '@pxlkit/ui-kit';

const PLANS = [
  { value: 'free', label: 'Free' },
  { value: 'pro', label: 'Pro' },
  { value: 'team', label: 'Team' },
];

export function Tones() {
  const [value, setValue] = useState('pro');
  return (
    <div className="grid grid-cols-2 gap-6">
      <PixelRadioGroup label="Neutral" tone="neutral" value={value} options={PLANS} onChange={setValue} />
      <PixelRadioGroup label="Green" tone="green" value={value} options={PLANS} onChange={setValue} />
      <PixelRadioGroup label="Cyan" tone="cyan" value={value} options={PLANS} onChange={setValue} />
      <PixelRadioGroup label="Gold" tone="gold" value={value} options={PLANS} onChange={setValue} />
      <PixelRadioGroup label="Red" tone="red" value={value} options={PLANS} onChange={setValue} />
      <PixelRadioGroup label="Purple" tone="purple" value={value} options={PLANS} onChange={setValue} />
      <PixelRadioGroup label="Pink" tone="pink" value={value} options={PLANS} onChange={setValue} />
    </div>
  );
}

Surfaces

import { useState } from 'react';
import { PixelRadioGroup } from '@pxlkit/ui-kit';

const PLANS = [
  { value: 'free', label: 'Free' },
  { value: 'pro', label: 'Pro' },
  { value: 'team', label: 'Team' },
];

export function Surfaces() {
  const [pixel, setPixel] = useState('pro');
  const [linear, setLinear] = useState('pro');
  return (
    <div className="grid grid-cols-2 gap-6">
      <PixelRadioGroup
        label="Pixel surface"
        surface="pixel"
        value={pixel}
        options={PLANS}
        onChange={setPixel}
      />
      <PixelRadioGroup
        label="Linear surface"
        surface="linear"
        value={linear}
        options={PLANS}
        onChange={setLinear}
      />
    </div>
  );
}

Disabled

import { PixelRadioGroup } from '@pxlkit/ui-kit';

const PLANS = [
  { value: 'free', label: 'Free' },
  { value: 'pro', label: 'Pro' },
  { value: 'team', label: 'Team' },
];

export function Disabled() {
  return (
    <PixelRadioGroup
      label="Plan (locked)"
      value="pro"
      options={PLANS}
      onChange={() => {}}
      disabled
    />
  );
}

Required

import { useState } from 'react';
import { PixelRadioGroup } from '@pxlkit/ui-kit';

const PLANS = [
  { value: 'free', label: 'Free' },
  { value: 'pro', label: 'Pro' },
  { value: 'team', label: 'Team' },
];

export function Required() {
  const [value, setValue] = useState('');
  return (
    <PixelRadioGroup
      label="Pick a plan to continue"
      value={value}
      options={PLANS}
      onChange={setValue}
      required
    />
  );
}

With form name

import { useState } from 'react';
import { PixelRadioGroup } from '@pxlkit/ui-kit';

const PLANS = [
  { value: 'free', label: 'Free' },
  { value: 'pro', label: 'Pro' },
  { value: 'team', label: 'Team' },
];

export function WithFormName() {
  const [value, setValue] = useState('pro');
  return (
    <form>
      <PixelRadioGroup
        label="Plan"
        name="plan"
        value={value}
        options={PLANS}
        onChange={setValue}
        required
      />
    </form>
  );
}

Related