diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 19f7f51a..2216ebbe 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -35,6 +35,7 @@
   - [Progress Bar](/components/progress-bar.md)
   - [Progress Ring](/components/progress-ring.md)
   - [Radio](/components/radio.md)
+  - [Radio Group](/components/radio-group.md)
   - [Range](/components/range.md)
   - [Rating](/components/rating.md)
   - [Responsive Embed](/components/responsive-embed.md)
diff --git a/docs/components/radio-group.md b/docs/components/radio-group.md
new file mode 100644
index 00000000..71a528b7
--- /dev/null
+++ b/docs/components/radio-group.md
@@ -0,0 +1,29 @@
+# Radio Group
+
+[component-header:sl-radio-group]
+
+Radio Groups are used to group multiple radios so they function as a single control.
+
+```html preview
+<sl-radio-group label="Select an item">
+  <sl-radio value="1" checked>Item 1</sl-radio>
+  <sl-radio value="2">Item 2</sl-radio>
+  <sl-radio value="3">Item 3</sl-radio>
+</sl-radio-group>
+```
+
+## Examples
+
+### Hiding the Fieldset
+
+You can hide the fieldset and legend that wraps the radio group using the `no-fieldset` attribute. In this case, a label is still required for assistive devices to properly identify the control.
+
+```html preview
+<sl-radio-group label="Select an item" no-fieldset>
+  <sl-radio value="1" checked>Item 1</sl-radio>
+  <sl-radio value="2">Item 2</sl-radio>
+  <sl-radio value="3">Item 3</sl-radio>
+</sl-radio-group>
+```
+
+[component-metadata:sl-radio-group]
diff --git a/docs/components/radio.md b/docs/components/radio.md
index fd0084ae..86e6bbe1 100644
--- a/docs/components/radio.md
+++ b/docs/components/radio.md
@@ -4,39 +4,31 @@
 
 Radios allow the user to select one option from a group of many.
 
+Radios are designed to be used with [radio groups](/components/radio-group). As such, all of the examples on this page utilize them to demonstrate their correct usage.
+
 ```html preview
-<sl-radio>Radio</sl-radio>
+<sl-radio-group label="Select an option" no-fieldset>
+  <sl-radio value="1" checked>Option 1</sl-radio>
+  <sl-radio value="2">Option 2</sl-radio>
+  <sl-radio value="3">Option 3</sl-radio>
+</sl-radio-group>
 ```
 
 ?> This component doesn't work with standard forms. Use [`<sl-form>`](/components/form.md) instead.
 
 ## Examples
 
-### Checked
-
-Use the `checked` attribute to activate the radio.
-
-```html preview
-<sl-radio checked>Checked</sl-radio>
-```
-
 ### Disabled
 
-Use the `disabled` attribute to disable the radio.
+Use the `disabled` attribute to disable a radio.
 
 ```html preview
-<sl-radio disabled>Disabled</sl-radio>
-```
-
-### Grouping Radios
-
-Radios are grouped based on their `name` attribute and scoped to the nearest form.
-
-```html preview
-<sl-radio name="option" checked>Option 1</sl-radio><br>
-<sl-radio name="option">Option 2</sl-radio><br>
-<sl-radio name="option">Option 3</sl-radio><br>
-<sl-radio name="option">Option 4</sl-radio>
+<sl-radio-group label="Select an option" no-fieldset>
+  <sl-radio value="1" checked>Option 1</sl-radio>
+  <sl-radio value="2">Option 2</sl-radio>
+  <sl-radio value="3">Option 3</sl-radio>
+  <sl-radio value="4" disabled>Disabled</sl-radio>
+</sl-radio-group>
 ```
 
 [component-metadata:sl-radio]
diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md
index 018a8cc8..b6147a49 100644
--- a/docs/resources/changelog.md
+++ b/docs/resources/changelog.md
@@ -6,6 +6,11 @@ Components with the <sl-badge type="warning" pill>Experimental</sl-badge> badge
 
 _During the beta period, these restrictions may be relaxed in the event of a mission-critical bug._ 🐛
 
+## Next
+
+- 🚨 BREAKING: `sl-radio` components must be located inside an `sl-radio-group` for proper accessibility [#218](https://github.com/shoelace-style/shoelace/issues/218)
+- Added `sl-radio-group` component [#218](https://github.com/shoelace-style/shoelace/issues/218)
+
 ## 2.0.0-beta.37
 
 - Added `click()` method to `sl-checkbox`, `sl-radio`, and `sl-switch`
diff --git a/src/components/radio-group/radio-group.scss b/src/components/radio-group/radio-group.scss
new file mode 100644
index 00000000..0e1400fb
--- /dev/null
+++ b/src/components/radio-group/radio-group.scss
@@ -0,0 +1,37 @@
+@use '../../styles/component';
+@use '../../styles/mixins/hide';
+
+:host {
+  display: block;
+}
+
+.radio-group {
+  border: solid var(--sl-input-border-width) var(--sl-input-border-color);
+  border-radius: var(--sl-border-radius-medium);
+  padding: var(--sl-spacing-large);
+  padding-top: var(--sl-spacing-x-small);
+
+  .radio-group__label {
+    font-family: var(--sl-input-font-family);
+    font-size: var(--sl-input-font-size-medium);
+    font-weight: var(--sl-input-font-weight);
+    color: var(--sl-input-color);
+    padding: 0 var(--sl-spacing-xx-small);
+  }
+}
+
+::slotted(sl-radio:not(:last-of-type)) {
+  display: block;
+  margin-bottom: var(--sl-spacing-xx-small);
+}
+
+.radio-group--no-fieldset {
+  border: none;
+  padding: 0;
+  margin: 0;
+  min-width: 0;
+
+  .radio-group__label {
+    @include hide.visually-hidden;
+  }
+}
diff --git a/src/components/radio-group/radio-group.ts b/src/components/radio-group/radio-group.ts
new file mode 100644
index 00000000..b7c39840
--- /dev/null
+++ b/src/components/radio-group/radio-group.ts
@@ -0,0 +1,49 @@
+import { LitElement, html, unsafeCSS } from 'lit';
+import { customElement, property } from 'lit/decorators';
+import { classMap } from 'lit-html/directives/class-map';
+import styles from 'sass:./radio-group.scss';
+
+/**
+ * @since 2.0
+ * @status stable
+ *
+ * @slot - The default slot where radio controls are placed.
+ * @slot label - The radio group label. Required for proper accessibility. Alternatively, you can use the label prop.
+ *
+ * @part base - The component's base wrapper.
+ * @part label - The radio group label.
+ */
+@customElement('sl-radio-group')
+export default class SlRadioGroup extends LitElement {
+  static styles = unsafeCSS(styles);
+
+  /** The radio group label. Required for proper accessibility. Alternatively, you can use the label slot. */
+  @property() label = '';
+
+  /** Hides the fieldset and legend that surrounds the radio group. The label will still be read by screen readers. */
+  @property({ type: Boolean, attribute: 'no-fieldset' }) noFieldset = false;
+
+  render() {
+    return html`
+      <fieldset
+        part="base"
+        class=${classMap({
+          'radio-group': true,
+          'radio-group--no-fieldset': this.noFieldset
+        })}
+        role="radiogroup"
+      >
+        <legend part="label" class="radio-group__label">
+          <slot name="label">${this.label}</slot>
+        </legend>
+        <slot></slot>
+      </fieldset>
+    `;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'sl-radio-group': SlRadioGroup;
+  }
+}
diff --git a/src/components/radio/radio.ts b/src/components/radio/radio.ts
index e16f5cdc..fffb2058 100644
--- a/src/components/radio/radio.ts
+++ b/src/components/radio/radio.ts
@@ -83,11 +83,14 @@ export default class SlRadio extends LitElement {
   }
 
   getAllRadios() {
-    const form = this.closest('sl-form, form') || document.body;
+    const radioGroup = this.closest('sl-radio-group');
 
-    if (!this.name) return [];
+    // Radios must be part of a radio group
+    if (!radioGroup) {
+      return [];
+    }
 
-    return [...form.querySelectorAll('sl-radio')].filter((radio: this) => radio.name === this.name) as this[];
+    return [...radioGroup.querySelectorAll('sl-radio')].filter((radio: this) => radio.name === this.name) as this[];
   }
 
   getSiblingRadios() {
@@ -171,8 +174,8 @@ export default class SlRadio extends LitElement {
             value=${ifDefined(this.value)}
             ?checked=${this.checked}
             ?disabled=${this.disabled}
-            role="radio"
             aria-checked=${this.checked ? 'true' : 'false'}
+            aria-disabled=${this.disabled ? 'true' : 'false'}
             aria-labelledby=${this.labelId}
             @click=${this.handleClick}
             @blur=${this.handleBlur}
diff --git a/src/shoelace.ts b/src/shoelace.ts
index a3af2188..482edb3f 100644
--- a/src/shoelace.ts
+++ b/src/shoelace.ts
@@ -29,6 +29,7 @@ export { default as SlMenuLabel } from './components/menu-label/menu-label';
 export { default as SlProgressBar } from './components/progress-bar/progress-bar';
 export { default as SlProgressRing } from './components/progress-ring/progress-ring';
 export { default as SlRadio } from './components/radio/radio';
+export { default as SlRadioGroup } from './components/radio-group/radio-group';
 export { default as SlRange } from './components/range/range';
 export { default as SlRating } from './components/rating/rating';
 export { default as SlRelativeTime } from './components/relative-time/relative-time';