diff --git a/addons/rose/addon/components/rose/form/fieldset/index.hbs b/addons/rose/addon/components/rose/form/fieldset/index.hbs
new file mode 100644
index 000000000..685644822
--- /dev/null
+++ b/addons/rose/addon/components/rose/form/fieldset/index.hbs
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/addons/rose/addon/components/rose/form/fieldset/index.js b/addons/rose/addon/components/rose/form/fieldset/index.js
new file mode 100644
index 000000000..4e2a7790e
--- /dev/null
+++ b/addons/rose/addon/components/rose/form/fieldset/index.js
@@ -0,0 +1,11 @@
+import Component from '@glimmer/component';
+import { generateComponentID } from '../../../../utilities/component-auto-id';
+
+export default class RoseFormFieldsetComponent extends Component {
+ // =attributes
+
+ /**
+ * @type {string}
+ */
+ id = generateComponentID();
+}
diff --git a/addons/rose/addon/components/rose/form/fieldset/index.stories.mdx b/addons/rose/addon/components/rose/form/fieldset/index.stories.mdx
new file mode 100644
index 000000000..9dd61a4c6
--- /dev/null
+++ b/addons/rose/addon/components/rose/form/fieldset/index.stories.mdx
@@ -0,0 +1,29 @@
+import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
+import { hbs } from 'ember-cli-htmlbars';
+import { action } from '@storybook/addon-actions';
+import { text, boolean, select } from '@storybook/addon-knobs';
+
+
+
+# Form Fieldset
+
+Renders a simple fieldset with title, description and body.
+
+```html
+
+ <:title>Title
+ <:description>Description
+ <:body>Rest of the form
+
+```
+
+
+ {{
+ template: hbs`
+
+ <:title>Title
+ <:description>Description
+ <:body>Rest of the form
+ `
+ }}
+
\ No newline at end of file
diff --git a/addons/rose/addon/styles/rose/components/form/_fieldset.scss b/addons/rose/addon/styles/rose/components/form/_fieldset.scss
new file mode 100644
index 000000000..8696ad562
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/form/_fieldset.scss
@@ -0,0 +1,14 @@
+@use '../../variables/sizing';
+@use '../../utilities/type';
+
+.rose-form-fieldset {
+ .rose-form-fieldset-title {
+ @include type.type(l, bold);
+ }
+
+ .rose-form-fieldset-description {
+ margin: sizing.rems(xxs) 0;
+ @include type.type(xs);
+ color: var(--ui-gray);
+ }
+}
diff --git a/addons/rose/addon/styles/rose/components/form/_index.scss b/addons/rose/addon/styles/rose/components/form/_index.scss
index 815a1aaf5..73137ac8d 100644
--- a/addons/rose/addon/styles/rose/components/form/_index.scss
+++ b/addons/rose/addon/styles/rose/components/form/_index.scss
@@ -5,3 +5,4 @@
@import 'checkbox';
@import 'select';
@import 'radio';
+@import 'fieldset';
diff --git a/addons/rose/app/components/rose/form/fieldset.js b/addons/rose/app/components/rose/form/fieldset.js
new file mode 100644
index 000000000..9619ad95c
--- /dev/null
+++ b/addons/rose/app/components/rose/form/fieldset.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/form/fieldset';
diff --git a/addons/rose/tests/integration/components/rose/form/fieldset-test.js b/addons/rose/tests/integration/components/rose/form/fieldset-test.js
new file mode 100644
index 000000000..d2e2742c1
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/form/fieldset-test.js
@@ -0,0 +1,59 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, find } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+
+module('Integration | Component | rose/form/fieldset', function (hooks) {
+ setupRenderingTest(hooks);
+
+ // Global values for mocking
+ this.mockTitle = 'Title';
+ this.mockDescription = 'Description';
+ this.mockBody = 'Rest of the form';
+
+ hooks.beforeEach(async function () {
+ // Render the component before each test
+ await render(hbs`
+
+ <:title>{{this.mockTitle}}
+ <:description>{{this.mockDescription}}
+ <:body>{{this.mockBody}}
+
+ `);
+ });
+
+ test('it renders', async function (assert) {
+ assert.expect(4);
+ assert.ok(find('.rose-form-fieldset'));
+ assert.equal(
+ find('.rose-form-fieldset-title').textContent.trim(),
+ this.mockTitle
+ );
+ assert.equal(
+ find('.rose-form-fieldset-description').textContent.trim(),
+ this.mockDescription
+ );
+ assert.equal(
+ find('.rose-form-fieldset-body').textContent.trim(),
+ this.mockBody
+ );
+ });
+
+ test('it supports aria description for title', async function (assert) {
+ assert.expect(2);
+ const titleElement = find('.rose-form-fieldset-title');
+ const descriptionElement = find('.rose-form-fieldset-description');
+
+ assert.equal(
+ titleElement.getAttribute('aria-describedBy'),
+ descriptionElement.id,
+ 'Title is described by description'
+ );
+
+ assert.equal(
+ descriptionElement.getAttribute('aria-labelledBy'),
+ titleElement.id,
+ 'Description is labelled by title'
+ );
+ });
+});