diff --git a/addon/components/radio-button-input.hbs b/addon/components/radio-button-input.hbs
new file mode 100644
index 00000000..8ef143f3
--- /dev/null
+++ b/addon/components/radio-button-input.hbs
@@ -0,0 +1 @@
+
diff --git a/addon/components/radio-button-input.js b/addon/components/radio-button-input.js
new file mode 100644
index 00000000..56f2fa82
--- /dev/null
+++ b/addon/components/radio-button-input.js
@@ -0,0 +1,33 @@
+import Component from '@glimmer/component';
+import { once } from '@ember/runloop';
+import { action } from '@ember/object';
+
+/**
+ * The `` itself.
+ *
+ * A native replacement for `ember-radio-button`'s component of the same name, kept API- and
+ * DOM-identical so existing call sites and CSS are unaffected. See `radio-button.js`.
+ */
+export default class RadioButtonInputComponent extends Component {
+ /**
+ * `aria-checked` wants a string, and only when `checked` is genuinely boolean — an undefined
+ * `@checked` must leave the attribute off rather than render "undefined".
+ */
+ get checkedStr() {
+ const checked = this.args.checked;
+
+ if (typeof checked === 'boolean') {
+ return checked.toString();
+ }
+
+ return null;
+ }
+
+ @action change() {
+ if (this.args.groupValue !== this.args.value) {
+ // Scheduled rather than called directly: a radio group swaps two inputs in the same
+ // interaction, and `once` collapses that into a single report of the new value.
+ once(this.args, 'changed', this.args.value);
+ }
+ }
+}
diff --git a/addon/components/radio-button.hbs b/addon/components/radio-button.hbs
new file mode 100644
index 00000000..e0da2ace
--- /dev/null
+++ b/addon/components/radio-button.hbs
@@ -0,0 +1,40 @@
+{{! template-lint-disable no-autofocus-attribute no-positive-tabindex }}
+{{! Both rules exist to stop an author HARDCODING these. Here they are pass-through bindings —
+ the value is whatever the caller supplied, and dropping them would change the component's
+ argument list. `ember-radio-button`, which this replaces, forwarded both. }}
+{{#if (has-block)}}
+
+{{else}}
+
+{{/if}}
diff --git a/addon/components/radio-button.js b/addon/components/radio-button.js
new file mode 100644
index 00000000..5aff5c56
--- /dev/null
+++ b/addon/components/radio-button.js
@@ -0,0 +1,51 @@
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import { isEqual } from '@ember/utils';
+
+/**
+ * A radio button bound to a group value.
+ *
+ * This is a native replacement for the `ember-radio-button` addon, which was pinned to a
+ * prerelease (3.0.0-beta.1) because its last stable release references the removed `Ember` global
+ * and throws on Ember 5. The component name, arguments and rendered DOM are identical, so call
+ * sites and stylesheets did not change.
+ *
+ * Arguments (all optional except `@value` and `@groupValue`):
+ * @value this button's value
+ * @groupValue the currently selected value; the button is checked when the two are equal
+ * @changed called with this button's value when it becomes selected
+ * @name @disabled @required @autofocus @tabindex forwarded to the input
+ * @radioClass class for the input, @radioId its id
+ * @classNames extra classes for the wrapping label (string or array)
+ * @checkedClass class added to the label while checked, default "checked"
+ * @ariaLabelledby @ariaDescribedby
+ *
+ * With a block it renders a `