> ## Documentation Index
> Fetch the complete documentation index at: https://www.beeq.design/llms.txt
> Use this file to discover all available pages before exploring further.

# Radio

> Radio buttons let users select exactly one option from a set of mutually exclusive choices.

export const CodeLivePreview = ({code, children, height, removePadding = false, mode = 'shadow'}) => {
  const previewRef = useRef(null);
  const BEEQ_ESM_URL = 'https://esm.sh/@beeq/core/dist/beeq/beeq.esm.js';
  const BEEQ_CSS_URL = 'https://esm.sh/@beeq/core/dist/beeq/beeq.css';
  const BEEQ_ICONS_URL = 'https://esm.sh/@beeq/core/dist/beeq/svg';
  const ensureParentBeeqRuntime = () => {
    if (document.querySelector('script[data-beeq-parent-runtime]')) return;
    const script = document.createElement('script');
    script.type = 'module';
    script.src = BEEQ_ESM_URL;
    script.dataset.beeqParentRuntime = BEEQ_ICONS_URL;
    document.head.appendChild(script);
  };
  const executeSnippetScripts = (root, runtimeWindow, previewRootArg, skipAttr) => {
    root.querySelectorAll('script').forEach(oldScript => {
      if (skipAttr && oldScript.hasAttribute(skipAttr)) return;
      if (oldScript.src) {
        const newScript = runtimeWindow.document.createElement('script');
        newScript.src = oldScript.src;
        oldScript.replaceWith(newScript);
        return;
      }
      runtimeWindow.Function('previewRoot', oldScript.textContent || '')(previewRootArg);
      oldScript.remove();
    });
  };
  const syncIframeMode = iframeDoc => {
    const root = document.documentElement;
    const explicitMode = root.getAttribute('bq-mode');
    const isDark = root.classList.contains('dark');
    const resolvedMode = explicitMode || (isDark ? 'dark' : 'light');
    iframeDoc.documentElement.setAttribute('bq-mode', resolvedMode);
    iframeDoc.body.setAttribute('bq-mode', resolvedMode);
  };
  useEffect(() => {
    if (mode === 'shadow') ensureParentBeeqRuntime();
  }, [mode]);
  useEffect(() => {
    const container = previewRef.current;
    if (!container) return;
    if (removePadding) container.style.padding = '0';
    container.innerHTML = '';
    if (mode === 'iframe') {
      const iframe = document.createElement('iframe');
      iframe.className = 'preview-iframe';
      iframe.style.width = '100%';
      iframe.style.border = '0';
      iframe.style.display = 'block';
      iframe.style.minHeight = height ?? '0px';
      iframe.setAttribute('title', 'Live code preview');
      iframe.setAttribute('loading', 'lazy');
      iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
      container.appendChild(iframe);
      const iframeDoc = iframe.contentDocument;
      const iframeWin = iframe.contentWindow;
      if (!iframeDoc || !iframeWin) return;
      iframeDoc.open();
      iframeDoc.write(['<!doctype html>', '<html>', '<head>', '  <meta charset="utf-8" />', '  <meta name="viewport" content="width=device-width, initial-scale=1" />', '  <style>html,body{margin:0;padding:0;min-height:100%;}</style>', `  <link rel="stylesheet" href="${BEEQ_CSS_URL}" />`, `  <script type="module" src="${BEEQ_ESM_URL}" data-beeq-iframe-runtime="${BEEQ_ICONS_URL}"></script>`, '</head>', '<body>', code, '</body>', '</html>'].join('\n'));
      iframeDoc.close();
      syncIframeMode(iframeDoc);
      const modeObserver = new MutationObserver(() => syncIframeMode(iframeDoc));
      modeObserver.observe(document.documentElement, {
        attributes: true,
        attributeFilter: ['class', 'bq-mode']
      });
      executeSnippetScripts(iframeDoc, iframeWin, iframeDoc, 'data-beeq-iframe-runtime');
      return () => modeObserver.disconnect();
    }
    const shadowRoot = container.shadowRoot ?? container.attachShadow({
      mode: 'open'
    });
    if (!shadowRoot.adoptedStyleSheets.length) {
      const fixSheet = new CSSStyleSheet();
      fixSheet.replaceSync(`
        bq-dropdown::part(panel),
        bq-panel::part(panel),
        bq-select::part(panel),
        bq-date-picker::part(panel) { z-index: 9999; }
        bq-tooltip::part(panel) { position: absolute; }
      `);
      shadowRoot.adoptedStyleSheets = [fixSheet];
    }
    shadowRoot.innerHTML = [`<link rel="stylesheet" href="${BEEQ_CSS_URL}">`, code].join('');
    executeSnippetScripts(shadowRoot, window, shadowRoot);
    return undefined;
  }, [code, mode, height]);
  return <div className="code-live-preview not-prose">
      {}
      <div className="preview" ref={previewRef} style={{
    minHeight: height
  }} />

      {}
      {children && <div className="code">{children}</div>}
    </div>;
};

export const CardTile = ({title, href, imageLightSrc, imageDarkSrc, children, enableImgZoom}) => {
  if (!href) {
    return <div className="card-tile block font-normal group relative my-2 ring-2 ring-transparent rounded-2xl overflow-hidden w-full">
        {imageLightSrc && <img className="w-full m-0 block dark:hidden" src={imageLightSrc} alt={title} {...enableImgZoom ? {
      zoom: true
    } : {
      noZoom: true
    }} />}
        {imageDarkSrc && <img className="w-full m-0 hidden dark:block" src={imageDarkSrc} alt={title} {...enableImgZoom ? {
      zoom: true
    } : {
      noZoom: true
    }} />}
        {title && <h2 className="mt-4 mb-2 px-6 text-base font-semibold">{title}</h2>}
        <div className="px-6 pb-5">{children}</div>
      </div>;
  }
  return <a href={href} className="card-tile block font-normal group relative my-2 ring-2 ring-transparent rounded-2xl overflow-hidden w-full cursor-pointer">
      {imageLightSrc && <img className="w-full m-0 block dark:hidden" src={imageLightSrc} alt={title} noZoom />}
      {imageDarkSrc && <img className="w-full m-0 hidden dark:block" src={imageDarkSrc} alt={title} noZoom />}
      {title && <h2 className="mt-4 mb-2 px-6 text-base font-semibold">{title}</h2>}
      <div className="px-6 pb-5">{children}</div>
    </a>;
};

<Frame className="px-4 py-4" caption="Radio component overview">
  <img className="block dark:hidden" src="https://mintlify.s3.us-west-1.amazonaws.com/beeq/components/images/radio/radio-overview-light.svg" alt="BEEQ Radio component overview" />

  <img className="hidden dark:block" src="https://mintlify.s3.us-west-1.amazonaws.com/beeq/components/images/radio/radio-overview-dark.svg" alt="BEEQ Radio component overview" />
</Frame>

Radio buttons present a list of options where only one can be active at a time. When a user selects a new option, the previous selection is cleared automatically. Use them inside a `bq-radio-group` to connect the options and manage state.

<Note>
  Always wrap `bq-radio` elements inside a `bq-radio-group`. The group manages selection state, keyboard navigation, form association, and accessibility — an isolated `bq-radio` cannot do this on its own.
</Note>

## When to use

<CardGroup cols={2}>
  <Card>
    <span className="flex items-center mr-2 text-lg font-medium" role="heading">
      <Icon className="mr-2" icon="thumbs-up" iconType="solid" size={20} color="var(--bq-stroke--success)" />

      Use radio buttons when
    </span>

    * Users must select exactly one option from a short list (2–7 items)
    * All available options need to be visible so users can compare them before choosing
    * The choice persists until the user actively changes it
    * An option needs to trigger a visible change in the surrounding form or UI
  </Card>

  <Card>
    <span className="flex items-center mr-2 text-lg font-medium" role="heading">
      <Icon className="mr-2" icon="thumbs-down" iconType="solid" size={20} color="var(--bq-stroke--danger)" />

      Do not use radio buttons when
    </span>

    * Multiple selections are allowed — use [Checkbox](/components/checkbox) instead
    * The list is long and searchable — use a [Dropdown](/components/dropdown) instead
    * A binary on/off toggle is all that is needed — a toggle or checkbox is clearer
    * The options are so numerous that displaying them all creates visual noise
  </Card>
</CardGroup>

## Patterns

<CardGroup cols={2}>
  <Card title="Default">
    By default, a radio button remains unchecked, implying it remains inactive until selected.
  </Card>

  <Card title="Selected">
    Once a radio button is selected, it becomes active, signifying its chosen status.
  </Card>
</CardGroup>

## Anatomy

<Frame className="px-4 py-4" caption="Radio component anatomy">
  <img className="block dark:hidden" src="https://mintlify.s3.us-west-1.amazonaws.com/beeq/components/images/radio/radio-anatomy-light.svg" alt="BEEQ Radio component anatomy" />

  <img className="hidden dark:block" src="https://mintlify.s3.us-west-1.amazonaws.com/beeq/components/images/radio/radio-anatomy-dark.svg" alt="BEEQ Radio component anatomy" />
</Frame>

A radio button consists of a circular icon and a text label. The icon changes appearance depending on whether the option is selected or unselected.

| Part  | Element           | Description                                                                        |
| ----- | ----------------- | ---------------------------------------------------------------------------------- |
| **1** | Icon (selected)   | The filled circular control shown when the radio is the active choice in the group |
| **2** | Label text        | The `<span>` that holds the option text and extends the clickable area             |
| **3** | Icon (unselected) | The empty circular control shown when the radio is not the active choice           |

## Design guidelines

<CardGroup cols={2}>
  <CardTile title="Vertical orientation (default)" imageLightSrc="/components/images/radio/radio-orientation-vertical-light.svg" imageDarkSrc="/components/images/radio/radio-orientation-vertical-dark.svg">
    Stack options vertically when the labels are longer than a few words. Vertical lists are easier to scan and less prone to accidental selection.
  </CardTile>

  <CardTile title="Horizontal orientation" imageLightSrc="/components/images/radio/radio-orientation-horizontal-light.svg" imageDarkSrc="/components/images/radio/radio-orientation-horizontal-dark.svg">
    Arrange options in a row when the labels are short (one or two words) and the total number of choices is small. Use this for compact filters or sort controls.
  </CardTile>
</CardGroup>

<CardGroup cols={2}>
  <CardTile title="With fieldset" imageLightSrc="/components/images/radio/radio-fieldset-light.svg" imageDarkSrc="/components/images/radio/radio-fieldset-dark.svg">
    Enable `fieldset` on the group to render a visible border and legend around the options. Use this when the radio group is one of several form sections that need visual separation.
  </CardTile>

  <CardTile title="Background on hover" imageLightSrc="/components/images/radio/radio-background-hover-light.svg" imageDarkSrc="/components/images/radio/radio-background-hover-dark.svg">
    Enable `background-on-hover` to show a filled background on the label row when the user hovers. Use this to increase the perceived click area in spacious layouts.
  </CardTile>
</CardGroup>

## Usage

### Default

Wrap `bq-radio` elements inside a `bq-radio-group`. Set a matching `value` on the group to pre-select one option. The group handles all state synchronization.

<CodeLivePreview
  mode="shadow"
  code={`
<bq-radio-group name="group" value="option1">
<span slot="label">Label</span>
<bq-radio value="option1">Radio option 1</bq-radio>
<bq-radio value="option2">Radio option 2</bq-radio>
<bq-radio value="option3">Radio option 3</bq-radio>
</bq-radio-group>
`}
>
  <CodeGroup>
    ```html HTML icon="html5" theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <bq-radio-group name="group" value="option1">
      <span slot="label">Label</span>
      <bq-radio value="option1">Radio option 1</bq-radio>
      <bq-radio value="option2">Radio option 2</bq-radio>
      <bq-radio value="option3">Radio option 3</bq-radio>
    </bq-radio-group>
    ```

    ```jsx React icon="react" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { BqRadio, BqRadioGroup } from "@beeq/react";

    <BqRadioGroup name="group" value="option1">
      <span slot="label">Label</span>
      <BqRadio value="option1">Radio option 1</BqRadio>
      <BqRadio value="option2">Radio option 2</BqRadio>
      <BqRadio value="option3">Radio option 3</BqRadio>
    </BqRadioGroup>
    ```

    ```ts Angular icon="angular" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { Component } from "@angular/core";
    import { BqRadio, BqRadioGroup } from "@beeq/angular/standalone";

    @Component({
      selector: "app-root",
      standalone: true,
      imports: [BqRadio, BqRadioGroup],
      template: `
        <bq-radio-group name="group" value="option1">
          <span slot="label">Label</span>
          <bq-radio value="option1">Radio option 1</bq-radio>
          <bq-radio value="option2">Radio option 2</bq-radio>
          <bq-radio value="option3">Radio option 3</bq-radio>
        </bq-radio-group>
      `
    })
    export class AppComponent {}
    ```

    ```vue Vue icon="vuejs" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <template>
      <BqRadioGroup name="group" value="option1">
        <span slot="label">Label</span>
        <BqRadio value="option1">Radio option 1</BqRadio>
        <BqRadio value="option2">Radio option 2</BqRadio>
        <BqRadio value="option3">Radio option 3</BqRadio>
      </BqRadioGroup>
    </template>

    <script setup>
    import { BqRadio, BqRadioGroup } from "@beeq/vue";
    </script>
    ```
  </CodeGroup>
</CodeLivePreview>

## Options

### Disabled

Set `disabled` on the group to prevent all interaction. You can also disable individual `bq-radio` elements when only specific options are unavailable.

<CodeLivePreview
  mode="shadow"
  code={`
<style>
:host {
  gap: var(--bq-spacing-l) !important;
}
</style>
<bq-radio-group name="group-disabled" value="option1" disabled>
<span slot="label">Label</span>
<bq-radio value="option1">Radio option 1</bq-radio>
<bq-radio value="option2">Radio option 2</bq-radio>
<bq-radio value="option3">Radio option 3</bq-radio>
</bq-radio-group>
<bq-radio-group name="group-partial" value="option1">
<span slot="label">Label</span>
<bq-radio value="option1">Radio option 1</bq-radio>
<bq-radio value="option2" disabled>Radio option 2</bq-radio>
<bq-radio value="option3">Radio option 3</bq-radio>
</bq-radio-group>
`}
>
  <CodeGroup>
    ```html HTML icon="html5" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <!-- Fully disabled group -->
    <bq-radio-group name="group-disabled" value="option1" disabled>
      <span slot="label">Label</span>
      <bq-radio value="option1">Radio option 1</bq-radio>
      <bq-radio value="option2">Radio option 2</bq-radio>
      <bq-radio value="option3">Radio option 3</bq-radio>
    </bq-radio-group>

    <!-- Individual radio disabled -->
    <bq-radio-group name="group-partial" value="option1">
      <span slot="label">Label</span>
      <bq-radio value="option1">Radio option 1</bq-radio>
      <bq-radio value="option2" disabled>Radio option 2</bq-radio>
      <bq-radio value="option3">Radio option 3</bq-radio>
    </bq-radio-group>
    ```

    ```jsx React icon="react" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { BqRadio, BqRadioGroup } from "@beeq/react";

    {/* Fully disabled group */}
    <BqRadioGroup name="group-disabled" value="option1" disabled>
      <span slot="label">Label</span>
      <BqRadio value="option1">Radio option 1</BqRadio>
      <BqRadio value="option2">Radio option 2</BqRadio>
      <BqRadio value="option3">Radio option 3</BqRadio>
    </BqRadioGroup>

    {/* Individual radio disabled */}
    <BqRadioGroup name="group-partial" value="option1">
      <span slot="label">Label</span>
      <BqRadio value="option1">Radio option 1</BqRadio>
      <BqRadio value="option2" disabled>Radio option 2</BqRadio>
      <BqRadio value="option3">Radio option 3</BqRadio>
    </BqRadioGroup>
    ```

    ```ts Angular icon="angular" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { Component } from "@angular/core";
    import { BqRadio, BqRadioGroup } from "@beeq/angular/standalone";

    @Component({
      selector: "app-root",
      standalone: true,
      imports: [BqRadio, BqRadioGroup],
      template: `
        <!-- Fully disabled group -->
        <bq-radio-group name="group-disabled" value="option1" disabled>
          <span slot="label">Label</span>
          <bq-radio value="option1">Radio option 1</bq-radio>
          <bq-radio value="option2">Radio option 2</bq-radio>
          <bq-radio value="option3">Radio option 3</bq-radio>
        </bq-radio-group>

        <!-- Individual radio disabled -->
        <bq-radio-group name="group-partial" value="option1">
          <span slot="label">Label</span>
          <bq-radio value="option1">Radio option 1</bq-radio>
          <bq-radio value="option2" disabled>Radio option 2</bq-radio>
          <bq-radio value="option3">Radio option 3</bq-radio>
        </bq-radio-group>
      `
    })
    export class AppComponent {}
    ```

    ```vue Vue icon="vuejs" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <template>
      <!-- Fully disabled group -->
      <BqRadioGroup name="group-disabled" value="option1" disabled>
        <span slot="label">Label</span>
        <BqRadio value="option1">Radio option 1</BqRadio>
        <BqRadio value="option2">Radio option 2</BqRadio>
        <BqRadio value="option3">Radio option 3</BqRadio>
      </BqRadioGroup>

      <!-- Individual radio disabled -->
      <BqRadioGroup name="group-partial" value="option1">
        <span slot="label">Label</span>
        <BqRadio value="option1">Radio option 1</BqRadio>
        <BqRadio value="option2" disabled>Radio option 2</BqRadio>
        <BqRadio value="option3">Radio option 3</BqRadio>
      </BqRadioGroup>
    </template>

    <script setup>
    import { BqRadio, BqRadioGroup } from "@beeq/vue";
    </script>
    ```
  </CodeGroup>
</CodeLivePreview>

### Horizontal

Use `orientation="horizontal"` to arrange options side by side. Best for short labels and a small number of choices where vertical space is limited.

<CodeLivePreview
  mode="shadow"
  code={`
<bq-radio-group name="group" value="option1" orientation="horizontal">
<span slot="label">Label</span>
<bq-radio value="option1">Radio option 1</bq-radio>
<bq-radio value="option2">Radio option 2</bq-radio>
<bq-radio value="option3">Radio option 3</bq-radio>
</bq-radio-group>
`}
>
  <CodeGroup>
    ```html HTML icon="html5" theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <bq-radio-group name="group" value="option1" orientation="horizontal">
      <span slot="label">Label</span>
      <bq-radio value="option1">Radio option 1</bq-radio>
      <bq-radio value="option2">Radio option 2</bq-radio>
      <bq-radio value="option3">Radio option 3</bq-radio>
    </bq-radio-group>
    ```

    ```jsx React icon="react" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { BqRadio, BqRadioGroup } from "@beeq/react";

    <BqRadioGroup name="group" value="option1" orientation="horizontal">
      <span slot="label">Label</span>
      <BqRadio value="option1">Radio option 1</BqRadio>
      <BqRadio value="option2">Radio option 2</BqRadio>
      <BqRadio value="option3">Radio option 3</BqRadio>
    </BqRadioGroup>
    ```

    ```ts Angular icon="angular" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { Component } from "@angular/core";
    import { BqRadio, BqRadioGroup } from "@beeq/angular/standalone";

    @Component({
      selector: "app-root",
      standalone: true,
      imports: [BqRadio, BqRadioGroup],
      template: `
        <bq-radio-group name="group" value="option1" orientation="horizontal">
          <span slot="label">Label</span>
          <bq-radio value="option1">Radio option 1</bq-radio>
          <bq-radio value="option2">Radio option 2</bq-radio>
          <bq-radio value="option3">Radio option 3</bq-radio>
        </bq-radio-group>
      `
    })
    export class AppComponent {}
    ```

    ```vue Vue icon="vuejs" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <template>
      <BqRadioGroup name="group" value="option1" orientation="horizontal">
        <span slot="label">Label</span>
        <BqRadio value="option1">Radio option 1</BqRadio>
        <BqRadio value="option2">Radio option 2</BqRadio>
        <BqRadio value="option3">Radio option 3</BqRadio>
      </BqRadioGroup>
    </template>

    <script setup>
    import { BqRadio, BqRadioGroup } from "@beeq/vue";
    </script>
    ```
  </CodeGroup>
</CodeLivePreview>

### Fieldset

Add the `fieldset` attribute to render a visible border and legend around the group. Use this when the radio group is one of several distinct sections in a form that need visual separation.

<CodeLivePreview
  mode="shadow"
  code={`
<bq-radio-group name="group" value="option1" fieldset>
<span slot="label">Label</span>
<bq-radio value="option1">Radio option 1</bq-radio>
<bq-radio value="option2">Radio option 2</bq-radio>
<bq-radio value="option3">Radio option 3</bq-radio>
</bq-radio-group>
`}
>
  <CodeGroup>
    ```html HTML icon="html5" theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <bq-radio-group name="group" value="option1" fieldset>
      <span slot="label">Label</span>
      <bq-radio value="option1">Radio option 1</bq-radio>
      <bq-radio value="option2">Radio option 2</bq-radio>
      <bq-radio value="option3">Radio option 3</bq-radio>
    </bq-radio-group>
    ```

    ```jsx React icon="react" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { BqRadio, BqRadioGroup } from "@beeq/react";

    <BqRadioGroup name="group" value="option1" fieldset>
      <span slot="label">Label</span>
      <BqRadio value="option1">Radio option 1</BqRadio>
      <BqRadio value="option2">Radio option 2</BqRadio>
      <BqRadio value="option3">Radio option 3</BqRadio>
    </BqRadioGroup>
    ```

    ```ts Angular icon="angular" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { Component } from "@angular/core";
    import { BqRadio, BqRadioGroup } from "@beeq/angular/standalone";

    @Component({
      selector: "app-root",
      standalone: true,
      imports: [BqRadio, BqRadioGroup],
      template: `
        <bq-radio-group name="group" value="option1" fieldset>
          <span slot="label">Label</span>
          <bq-radio value="option1">Radio option 1</bq-radio>
          <bq-radio value="option2">Radio option 2</bq-radio>
          <bq-radio value="option3">Radio option 3</bq-radio>
        </bq-radio-group>
      `
    })
    export class AppComponent {}
    ```

    ```vue Vue icon="vuejs" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <template>
      <BqRadioGroup name="group" value="option1" :fieldset="true">
        <span slot="label">Label</span>
        <BqRadio value="option1">Radio option 1</BqRadio>
        <BqRadio value="option2">Radio option 2</BqRadio>
        <BqRadio value="option3">Radio option 3</BqRadio>
      </BqRadioGroup>
    </template>

    <script setup>
    import { BqRadio, BqRadioGroup } from "@beeq/vue";
    </script>
    ```
  </CodeGroup>
</CodeLivePreview>

### Background on hover

Set `background-on-hover` on the group (or on individual `bq-radio` elements) to show a filled highlight on the label row when the user hovers. This increases the perceived click area in spacious layouts.

<CodeLivePreview
  mode="shadow"
  code={`
<bq-radio-group name="group" value="option1" background-on-hover>
<span slot="label">Label</span>
<bq-radio value="option1">Radio option 1</bq-radio>
<bq-radio value="option2">Radio option 2</bq-radio>
<bq-radio value="option3">Radio option 3</bq-radio>
</bq-radio-group>
`}
>
  <CodeGroup>
    ```html HTML icon="html5" theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <bq-radio-group name="group" value="option1" background-on-hover>
      <span slot="label">Label</span>
      <bq-radio value="option1">Radio option 1</bq-radio>
      <bq-radio value="option2">Radio option 2</bq-radio>
      <bq-radio value="option3">Radio option 3</bq-radio>
    </bq-radio-group>
    ```

    ```jsx React icon="react" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { BqRadio, BqRadioGroup } from "@beeq/react";

    <BqRadioGroup name="group" value="option1" backgroundOnHover>
      <span slot="label">Label</span>
      <BqRadio value="option1">Radio option 1</BqRadio>
      <BqRadio value="option2">Radio option 2</BqRadio>
      <BqRadio value="option3">Radio option 3</BqRadio>
    </BqRadioGroup>
    ```

    ```ts Angular icon="angular" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { Component } from "@angular/core";
    import { BqRadio, BqRadioGroup } from "@beeq/angular/standalone";

    @Component({
      selector: "app-root",
      standalone: true,
      imports: [BqRadio, BqRadioGroup],
      template: `
        <bq-radio-group name="group" value="option1" background-on-hover>
          <span slot="label">Label</span>
          <bq-radio value="option1">Radio option 1</bq-radio>
          <bq-radio value="option2">Radio option 2</bq-radio>
          <bq-radio value="option3">Radio option 3</bq-radio>
        </bq-radio-group>
      `
    })
    export class AppComponent {}
    ```

    ```vue Vue icon="vuejs" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <template>
      <BqRadioGroup name="group" value="option1" backgroundOnHover>
        <span slot="label">Label</span>
        <BqRadio value="option1">Radio option 1</BqRadio>
        <BqRadio value="option2">Radio option 2</BqRadio>
        <BqRadio value="option3">Radio option 3</BqRadio>
      </BqRadioGroup>
    </template>

    <script setup>
    import { BqRadio, BqRadioGroup } from "@beeq/vue";
    </script>
    ```
  </CodeGroup>
</CodeLivePreview>

### Form integration

`bq-radio-group` submits the selected radio value with the form. Use `required` and `required-validation-message` on the group when users must choose one option before submission.

<CodeLivePreview
  mode="iframe"
  height="25rem"
  removePadding
  code={`
<style>
body {
  padding: var(--bq-spacing-l);
}

.radio-form {
  display: grid;
  gap: var(--bq-spacing-m);
}

.radio-form__actions {
  display: flex;
  gap: var(--bq-spacing-s);
  justify-content: flex-end;
}

.radio-form__output {
  margin: 0;
  padding: var(--bq-spacing-s);
  border: var(--bq-stroke-s) solid var(--bq-stroke--tertiary);
  border-radius: var(--bq-radius--s);
  background: var(--bq-ui--secondary);
  color: var(--bq-text--primary);
  white-space: pre-wrap;
}

bq-radio-group::part(base) {
  border-color: var(--bq-stroke--primary);
  border-radius: var(--bq-radius--m);
  border-width: var(--bq-stroke-s);
}
</style>

<form class="radio-form" id="radio-form">
<bq-radio-group
  fieldset
  name="contactMethod"
  required
  required-validation-message="Choose a contact method"
>
  <span slot="label">Preferred contact method</span>
  <bq-radio value="email">Email</bq-radio>
  <bq-radio value="phone">Phone</bq-radio>
  <bq-radio value="chat">Chat</bq-radio>
</bq-radio-group>
<div class="radio-form__actions">
  <bq-button appearance="secondary" type="reset">Reset</bq-button>
  <bq-button type="button">Submit</bq-button>
</div>
<pre class="radio-form__output" id="radio-output">{}</pre>
</form>

<script>
(() => {
  const form = previewRoot.querySelector('#radio-form');
  const output = previewRoot.querySelector('#radio-output');
  const submitButton = form.querySelector('bq-button[type="button"]');

  const showFormData = () => {
    if (!form.reportValidity()) return;
    output.textContent = JSON.stringify(Object.fromEntries(new FormData(form).entries()), null, 2);
  };

  submitButton?.addEventListener('bqClick', (event) => {
    event.preventDefault();
    showFormData();
  });

  form.addEventListener('submit', (event) => {
    event.preventDefault();
    showFormData();
  });

  form.addEventListener('reset', () => {
    requestAnimationFrame(() => {
      output.textContent = '{}';
    });
  });
})();
</script>
`}
>
  <CodeGroup>
    ```html HTML icon="html5" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <form id="radio-form">
      <bq-radio-group
        fieldset
        name="contactMethod"
        required
        required-validation-message="Choose a contact method"
      >
        <span slot="label">Preferred contact method</span>
        <bq-radio value="email">Email</bq-radio>
        <bq-radio value="phone">Phone</bq-radio>
        <bq-radio value="chat">Chat</bq-radio>
      </bq-radio-group>
      <bq-button appearance="secondary" type="reset">Reset</bq-button>
      <bq-button type="submit">Submit</bq-button>
      <pre id="radio-output">{}</pre>
    </form>

    <script>
      const form = document.querySelector('#radio-form');
      const output = document.querySelector('#radio-output');

      form.addEventListener('submit', (event) => {
        event.preventDefault();
        output.textContent = JSON.stringify(Object.fromEntries(new FormData(form).entries()), null, 2);
      });

      form.addEventListener('reset', () => {
        requestAnimationFrame(() => {
          output.textContent = '{}';
        });
      });
    </script>
    ```

    ```tsx React icon="react" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { useState } from "react";
    import { BqButton, BqRadio, BqRadioGroup } from "@beeq/react";

    export default function RadioForm() {
      const [data, setData] = useState("{}");

      return (
        <form
          onReset={() => setData("{}")}
          onSubmit={(event) => {
            event.preventDefault();
            setData(JSON.stringify(Object.fromEntries(new FormData(event.currentTarget).entries()), null, 2));
          }}
        >
          <BqRadioGroup
            fieldset
            name="contactMethod"
            required
            requiredValidationMessage="Choose a contact method"
          >
            <span slot="label">Preferred contact method</span>
            <BqRadio value="email">Email</BqRadio>
            <BqRadio value="phone">Phone</BqRadio>
            <BqRadio value="chat">Chat</BqRadio>
          </BqRadioGroup>
          <BqButton appearance="secondary" type="reset">Reset</BqButton>
          <BqButton type="submit">Submit</BqButton>
          <pre>{data}</pre>
        </form>
      );
    }
    ```

    ```ts Angular icon="angular" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    import { Component } from "@angular/core";
    import { BqButton, BqRadio, BqRadioGroup } from "@beeq/angular/standalone";

    @Component({
      selector: "app-root",
      standalone: true,
      imports: [BqButton, BqRadio, BqRadioGroup],
      template: `
        <form (reset)="data = '{}'" (submit)="handleSubmit($event)">
          <bq-radio-group
            fieldset
            name="contactMethod"
            required
            required-validation-message="Choose a contact method"
          >
            <span slot="label">Preferred contact method</span>
            <bq-radio value="email">Email</bq-radio>
            <bq-radio value="phone">Phone</bq-radio>
            <bq-radio value="chat">Chat</bq-radio>
          </bq-radio-group>
          <bq-button appearance="secondary" type="reset">Reset</bq-button>
          <bq-button type="submit">Submit</bq-button>
          <pre>{{ data }}</pre>
        </form>
      `,
    })
    export class RadioFormComponent {
      data = "{}";

      handleSubmit(event: SubmitEvent): void {
        event.preventDefault();
        this.data = JSON.stringify(Object.fromEntries(new FormData(event.target as HTMLFormElement).entries()), null, 2);
      }
    }
    ```

    ```vue Vue icon="vuejs" expandable theme={"theme":{"light":"one-light","dark":"night-owl"}}
    <script setup lang="ts">
    import { ref } from "vue";
    import { BqButton, BqRadio, BqRadioGroup } from "@beeq/vue";

    const data = ref("{}");

    function handleSubmit(event: Event) {
      data.value = JSON.stringify(Object.fromEntries(new FormData(event.target as HTMLFormElement).entries()), null, 2);
    }
    </script>

    <template>
      <form @reset="data = '{}'" @submit.prevent="handleSubmit">
        <BqRadioGroup
          fieldset
          name="contactMethod"
          required
          requiredValidationMessage="Choose a contact method"
        >
          <span slot="label">Preferred contact method</span>
          <BqRadio value="email">Email</BqRadio>
          <BqRadio value="phone">Phone</BqRadio>
          <BqRadio value="chat">Chat</BqRadio>
        </BqRadioGroup>
        <BqButton appearance="secondary" type="reset">Reset</BqButton>
        <BqButton type="submit">Submit</BqButton>
        <pre>{{ data }}</pre>
      </form>
    </template>
    ```
  </CodeGroup>
</CodeLivePreview>

## Best practices

<CardGroup cols={2}>
  <Card>
    <span className="flex items-center mr-2 text-lg font-medium" role="heading">
      <Icon className="mr-2" icon="check" iconType="solid" size={20} color="var(--bq-stroke--success)" />

      Do
    </span>

    Always provide a group label via the `label` slot on `bq-radio-group` so screen readers announce the question or field name when focus enters the group.
  </Card>

  <Card>
    <span className="flex items-center mr-2 text-lg font-medium" role="heading">
      <Icon className="mr-2" icon="xmark" iconType="solid" size={20} color="var(--bq-stroke--danger)" />

      Don't
    </span>

    Leave the `label` slot empty — users relying on assistive technology will not know what the radio group is asking.
  </Card>

  <Card>
    <span className="flex items-center mr-2 text-lg font-medium" role="heading">
      <Icon className="mr-2" icon="check" iconType="solid" size={20} color="var(--bq-stroke--success)" />

      Do
    </span>

    Pre-select a sensible default by setting `value` on the group so the form always has a valid starting state and users understand which option is active on first view.
  </Card>

  <Card>
    <span className="flex items-center mr-2 text-lg font-medium" role="heading">
      <Icon className="mr-2" icon="xmark" iconType="solid" size={20} color="var(--bq-stroke--danger)" />

      Don't
    </span>

    Leave all options unselected when one choice is always required — an empty radio group can cause confusing form validation errors on submit.
  </Card>

  <Card>
    <span className="flex items-center mr-2 text-lg font-medium" role="heading">
      <Icon className="mr-2" icon="check" iconType="solid" size={20} color="var(--bq-stroke--success)" />

      Do
    </span>

    Write option labels as nouns or short phrases that clearly describe each choice so users can compare them at a glance.
  </Card>

  <Card>
    <span className="flex items-center mr-2 text-lg font-medium" role="heading">
      <Icon className="mr-2" icon="xmark" iconType="solid" size={20} color="var(--bq-stroke--danger)" />

      Don't
    </span>

    Use vague labels like "Option 1" or "Yes/No" when more descriptive text would make the choice immediately understandable without additional context.
  </Card>

  <Card>
    <span className="flex items-center mr-2 text-lg font-medium" role="heading">
      <Icon className="mr-2" icon="check" iconType="solid" size={20} color="var(--bq-stroke--success)" />

      Do
    </span>

    Use `disabled` on the entire group when the choice is unavailable in the current context, and explain why it is locked via nearby helper text.
  </Card>

  <Card>
    <span className="flex items-center mr-2 text-lg font-medium" role="heading">
      <Icon className="mr-2" icon="xmark" iconType="solid" size={20} color="var(--bq-stroke--danger)" />

      Don't
    </span>

    Disable individual radio options without a visible explanation — users will not know whether the option is temporarily locked or permanently unavailable.
  </Card>
</CardGroup>

## Accessibility

* **Built in:** `bq-radio-group` renders as a `<fieldset>` (when `fieldset` is set) with a `<legend>` linked to the `label` slot, giving screen readers a proper group label for the set.
* **Built in:** each `bq-radio` native `<input type="radio">` carries `aria-checked`, `aria-disabled`, and `aria-labelledby` pointing to its visible label element.
* **Built in:** arrow-key navigation is handled by the group — `ArrowDown` / `ArrowRight` move to the next radio, `ArrowUp` / `ArrowLeft` move to the previous one, wrapping at the ends. This matches the ARIA radio group keyboard pattern.
* **Built in:** only one radio in the group receives `tabindex="0"` at a time (the selected or first enabled option), keeping the tab stop count low.
* **Your responsibility:** always populate the `label` slot on `bq-radio-group` — the `aria-labelledby` reference on the group requires a visible label element to give the fieldset an accessible name.
* **Your responsibility:** provide meaningful text inside each `bq-radio` slot — the `aria-labelledby` on the native input points to the label span, so empty or icon-only labels are not announced.
* **Your responsibility:** when you disable the group, add visible helper text nearby explaining why the options are unavailable.

## API reference

### `bq-radio` properties

| Property            | Attribute             | Description                                                              | Type      | Default |
| ------------------- | --------------------- | ------------------------------------------------------------------------ | --------- | ------- |
| `backgroundOnHover` | `background-on-hover` | If `true`, the radio displays a background highlight on hover            | `boolean` | `false` |
| `checked`           | `checked`             | If `true`, the radio input is checked                                    | `boolean` | `false` |
| `disabled`          | `disabled`            | If `true`, the radio input is disabled                                   | `boolean` | `false` |
| `formId`            | `form-id`             | The ID of the `<form>` element the radio is associated with              | `string`  | —       |
| `name`              | `name`                | Name of the form control, submitted as part of a name/value pair         | `string`  | —       |
| `required`          | `required`            | If `true`, the user must select a value before the form can be submitted | `boolean` | `false` |
| `value`             | `value`               | The string value of the radio, submitted with the form                   | `string`  | —       |

### `bq-radio-group` properties

| Property                    | Attribute                     | Description                                                                               | Type                         | Default      |
| --------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------- | ------------ |
| `backgroundOnHover`         | `background-on-hover`         | If `true`, all radios in the group show a background on hover                             | `boolean`                    | `false`      |
| `debounceTime`              | `debounce-time`               | Delay in milliseconds before `bqChange` fires after a value change                        | `number`                     | `0`          |
| `disabled`                  | `disabled`                    | If `true`, all radio inputs in the group are disabled                                     | `boolean`                    | `false`      |
| `fieldset`                  | `fieldset`                    | If `true`, the group renders a visible fieldset border with a legend                      | `boolean`                    | `false`      |
| `name`                      | `name`                        | Name of the form control, submitted with the selected radio's value                       | `string`                     | —            |
| `orientation`               | `orientation`                 | Layout direction of the radio inputs                                                      | `'horizontal' \| 'vertical'` | `'vertical'` |
| `required`                  | `required`                    | If `true`, the radio group is required                                                    | `boolean`                    | `false`      |
| `requiredValidationMessage` | `required-validation-message` | Native form validation message shown when the group is required and no option is selected | `string`                     | —            |
| `value`                     | `value`                       | The value of the currently selected radio                                                 | `string`                     | —            |

### `bq-radio` methods

| Method             | Description                                                                  |
| ------------------ | ---------------------------------------------------------------------------- |
| `vClick()`         | Simulates a click on the native `<input>` — use instead of `element.click()` |
| `vFocus()`         | Sets focus on the native `<input>` — use instead of `element.focus()`        |
| `vBlur()`          | Removes focus from the native `<input>` — use instead of `element.blur()`    |
| `getNativeInput()` | Returns the native `<input>` HTML element used under the hood                |

### `bq-radio` events

| Event       | Description                                                                                                       |
| ----------- | ----------------------------------------------------------------------------------------------------------------- |
| `bqClick`   | Emitted when the radio is clicked; detail is `{ value: string; target: HTMLBqRadioElement }`                      |
| `bqFocus`   | Emitted when the radio receives focus                                                                             |
| `bqBlur`    | Emitted when the radio loses focus                                                                                |
| `bqKeyDown` | Emitted when a key is pressed while the radio is focused; detail is `{ key: string; target: HTMLBqRadioElement }` |

### `bq-radio-group` events

| Event      | Description                                                                                        |
| ---------- | -------------------------------------------------------------------------------------------------- |
| `bqChange` | Emitted when the selected radio changes; detail is `{ value: string; target: HTMLBqRadioElement }` |
| `bqBlur`   | Emitted when focus leaves the entire group                                                         |

### `bq-radio` slots

| Slot        | Description                                 |
| ----------- | ------------------------------------------- |
| *(default)* | The visible label text for the radio option |

### `bq-radio-group` slots

| Slot        | Description                                                      |
| ----------- | ---------------------------------------------------------------- |
| *(default)* | The `bq-radio` elements that make up the group                   |
| `label`     | The group's label text, rendered as a legend inside the fieldset |

### `bq-radio` shadow parts

| Part    | Description                                                        |
| ------- | ------------------------------------------------------------------ |
| `base`  | The `<label>` wrapper that contains the control and the label text |
| `input` | The native `<input type="radio">` element                          |
| `radio` | The `<div>` that renders the visible circular ring                 |
| `label` | The `<span>` that holds the option text content                    |

### `bq-radio-group` shadow parts

| Part    | Description                                                                      |
| ------- | -------------------------------------------------------------------------------- |
| `base`  | The `<fieldset>` or `<div>` wrapper that contains the label and all radio inputs |
| `label` | The `<legend>` that holds the group label slot content                           |
| `group` | The `<div>` that lays out the radio inputs                                       |

### CSS custom properties

| Variable                   | Description                                         | Default              |
| -------------------------- | --------------------------------------------------- | -------------------- |
| `--bq-radio--size`         | Size (width and height) of the radio control circle | `24px`               |
| `--bq-radio--border-width` | Border width of the radio control ring              | `var(--bq-stroke-m)` |

<Tip>
  Learn more about [styling with shadow parts](/guides/styles#component-shadow-dom-parts) and [CSS custom properties](/guides/styles#global-css-custom-properties).
</Tip>

## Resources

<CardGroup cols={2}>
  <Card horizontal title="Interactive playground" icon="code" href="https://storybook.beeq.design/?path=/story/components-radio-group--default">
    Explore Radio variants and states in Storybook
  </Card>

  <Card horizontal title="Source code" icon="github" href="https://github.com/Endava/BEEQ/tree/main/packages/beeq/src/components/radio">
    View the component source on GitHub
  </Card>
</CardGroup>
