Forms

Radio

Botón de opción para grupos de selección única

Preview

size
labels
required
error
disabled

Code

import css from "./Radio.module.css";

type Size = "xs" | "sm" | "md" | "lg";

interface ResponsiveSize {
  base: Size;
  mobile?: Size;
  tablet?: Size;
  desktop?: Size;
  wide?: Size;
}

interface RadioProps {
  size: Size | ResponsiveSize;
  name: string;
  value: string;
  label?: string;
  "aria-label"?: string;
  checked: boolean;
  required?: boolean;
  error?: boolean;
  disabled?: boolean;
  onChange: (value: string) => void;
}

function getSizeClasses(size: Size | ResponsiveSize): string {
  if (typeof size === "string") {
    return css[size];
  }

  const classes = [css[`${size.base}_base`]];

  if (size.mobile) classes.push(css[`${size.mobile}_mobile`]);
  if (size.tablet) classes.push(css[`${size.tablet}_tablet`]);
  if (size.desktop) classes.push(css[`${size.desktop}_desktop`]);
  if (size.wide) classes.push(css[`${size.wide}_wide`]);

  return classes.join(" ");
}

export default function Radio({
  size,
  name,
  value,
  label,
  "aria-label": ariaLabel,
  checked,
  required,
  error,
  disabled,
  onChange,
}: RadioProps) {
  const handleChange = () => {
    onChange(value);
  };

  const Wrapper = label ? "label" : "span";

  return (
    <Wrapper
      className={`
        ${css.radio}
        ${getSizeClasses(size)}
        ${checked ? css.checked : ""}
        ${error ? css.error : ""}
        ${disabled ? css.disabled : ""}
      `}
    >
      <input
        type="radio"
        name={name}
        value={value}
        checked={checked}
        required={required}
        disabled={disabled}
        aria-label={label ? undefined : ariaLabel}
        aria-invalid={error}
        onChange={handleChange}
      />
      <span className={css.radio_circle}>
        <span className={css.circle_dot}></span>
      </span>
      {label && (
        <span className={css.radio_label}>
          {label}
          {required && <span aria-hidden="true">*</span>}
        </span>
      )}
    </Wrapper>
  );
}

Props

PropTypeRequiredDescription
size"xs" | "sm" | "md" | "lg" | ResponsiveSizerequiredTamaño del control (círculo, dot y label). Fijo o responsive.
namestringrequiredNombre del grupo. Los radios con el mismo name forman un grupo.
valuestringrequiredValor que identifica esta opción dentro del grupo.
labelstring—Texto junto al radio. Sin label, queda solo el círculo.
aria-labelstring—Nombre del control para lectores de pantalla. Requerida si no hay label; con label se ignora.
requiredboolean—Marca el grupo como obligatorio (añade * al label y el required nativo).
errorboolean—Estado de error visual (borde rojo, aria-invalid).
disabledboolean—Desactiva la opción (no interactiva, opacidad reducida).
checkedbooleanrequiredSi esta opción es la seleccionada del grupo (controlado).
onChange(value: string) => voidrequiredCallback con el value de la opción elegida.