diff --git a/addons/rose/addon/components/rose/table.js b/addons/rose/addon/components/rose/table.js
new file mode 100644
index 000000000..bd991b9d8
--- /dev/null
+++ b/addons/rose/addon/components/rose/table.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../templates/components/rose/table';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/table.stories.mdx b/addons/rose/addon/components/rose/table.stories.mdx
new file mode 100644
index 000000000..df20dcb17
--- /dev/null
+++ b/addons/rose/addon/components/rose/table.stories.mdx
@@ -0,0 +1,57 @@
+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';
+
+
+
+# Table
+
+A table component to display data in structure of rows and columns. It supports `header` and `body` optional definitions. Each definition allows for a table row to be defined with table cells.
+
+```htmlbars
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+ {{
+ template: hbs`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ context: {}
+ }}
+
diff --git a/addons/rose/addon/components/rose/table/body.js b/addons/rose/addon/components/rose/table/body.js
new file mode 100644
index 000000000..2702ed983
--- /dev/null
+++ b/addons/rose/addon/components/rose/table/body.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/table/body';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/table/cell.js b/addons/rose/addon/components/rose/table/cell.js
new file mode 100644
index 000000000..3943c8054
--- /dev/null
+++ b/addons/rose/addon/components/rose/table/cell.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/table/cell';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/table/header.js b/addons/rose/addon/components/rose/table/header.js
new file mode 100644
index 000000000..bcc2ff040
--- /dev/null
+++ b/addons/rose/addon/components/rose/table/header.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/table/header';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/table/row.js b/addons/rose/addon/components/rose/table/row.js
new file mode 100644
index 000000000..4012b9afd
--- /dev/null
+++ b/addons/rose/addon/components/rose/table/row.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/table/row';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/styles/rose/components/_index.scss b/addons/rose/addon/styles/rose/components/_index.scss
index f8ba54fb1..432c10562 100644
--- a/addons/rose/addon/styles/rose/components/_index.scss
+++ b/addons/rose/addon/styles/rose/components/_index.scss
@@ -6,3 +6,4 @@
@import 'card';
@import 'page-header';
@import 'nav';
+@import 'table';
diff --git a/addons/rose/addon/styles/rose/components/table/_index.scss b/addons/rose/addon/styles/rose/components/table/_index.scss
new file mode 100644
index 000000000..4f75fd6b3
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/table/_index.scss
@@ -0,0 +1,17 @@
+@use '../../variables/sizing';
+@use '../../utilities/type';
+
+.rose-table {
+ @include type.type(xs, normal);
+ width: 100%;
+
+ .rose-table-header {
+ background-color: var(--ui-gray-subtler-5);
+ text-align: left;
+ }
+
+ .rose-table-cell {
+ border: sizing.rems(xxxxs) solid var(--ui-gray-subtler-3);
+ padding: sizing.rems(xs) sizing.rems(xxs);
+ }
+}
diff --git a/addons/rose/addon/templates/components/rose/table.hbs b/addons/rose/addon/templates/components/rose/table.hbs
new file mode 100644
index 000000000..1346d685c
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table.hbs
@@ -0,0 +1,10 @@
+
+ {{#if @caption}}
+ {{@caption}}
+ {{/if}}
+
+ {{yield (hash
+ header=(component 'rose/table/header')
+ body=(component 'rose/table/body')
+ )}}
+
diff --git a/addons/rose/addon/templates/components/rose/table/body.hbs b/addons/rose/addon/templates/components/rose/table/body.hbs
new file mode 100644
index 000000000..f82c64d04
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table/body.hbs
@@ -0,0 +1,5 @@
+
+ {{yield (hash
+ row=(component 'rose/table/row')
+ )}}
+
diff --git a/addons/rose/addon/templates/components/rose/table/cell.hbs b/addons/rose/addon/templates/components/rose/table/cell.hbs
new file mode 100644
index 000000000..cb7a4d31d
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table/cell.hbs
@@ -0,0 +1,11 @@
+{{#if @header}}
+
+{{else}}
+
+ {{@text}}
+ {{yield}}
+ |
+{{/if}}
diff --git a/addons/rose/addon/templates/components/rose/table/header.hbs b/addons/rose/addon/templates/components/rose/table/header.hbs
new file mode 100644
index 000000000..13344bc99
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table/header.hbs
@@ -0,0 +1,5 @@
+
diff --git a/addons/rose/addon/templates/components/rose/table/row.hbs b/addons/rose/addon/templates/components/rose/table/row.hbs
new file mode 100644
index 000000000..af67886a1
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table/row.hbs
@@ -0,0 +1,5 @@
+
+ {{yield (hash
+ cell=(component 'rose/table/cell' header=@header)
+ )}}
+
diff --git a/addons/rose/app/components/rose/table.js b/addons/rose/app/components/rose/table.js
new file mode 100644
index 000000000..9101f15ac
--- /dev/null
+++ b/addons/rose/app/components/rose/table.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table';
diff --git a/addons/rose/app/components/rose/table/body.js b/addons/rose/app/components/rose/table/body.js
new file mode 100644
index 000000000..bf58d0690
--- /dev/null
+++ b/addons/rose/app/components/rose/table/body.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table/body';
diff --git a/addons/rose/app/components/rose/table/cell.js b/addons/rose/app/components/rose/table/cell.js
new file mode 100644
index 000000000..7e3b6e8b8
--- /dev/null
+++ b/addons/rose/app/components/rose/table/cell.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table/cell';
diff --git a/addons/rose/app/components/rose/table/header.js b/addons/rose/app/components/rose/table/header.js
new file mode 100644
index 000000000..c0a585fe7
--- /dev/null
+++ b/addons/rose/app/components/rose/table/header.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table/header';
diff --git a/addons/rose/app/components/rose/table/row.js b/addons/rose/app/components/rose/table/row.js
new file mode 100644
index 000000000..91618b6a0
--- /dev/null
+++ b/addons/rose/app/components/rose/table/row.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table/row';
diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs
index 576a37395..e01c76049 100644
--- a/addons/rose/tests/dummy/app/templates/index.hbs
+++ b/addons/rose/tests/dummy/app/templates/index.hbs
@@ -205,5 +205,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/rose/tests/integration/components/rose/table-test.js b/addons/rose/tests/integration/components/rose/table-test.js
new file mode 100644
index 000000000..6f792bd70
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table-test.js
@@ -0,0 +1,39 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, find } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+
+module('Integration | Component | rose/table', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('table'));
+ assert.ok(find('.rose-table'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#table'));
+ });
+
+ test('it renders with caption', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('caption'));
+ assert.equal(find('caption').textContent.trim(), 'table caption');
+ });
+
+ test('it renders with header', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('thead'));
+ });
+
+ test('it renders with body', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('tbody'));
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/table/body-test.js b/addons/rose/tests/integration/components/rose/table/body-test.js
new file mode 100644
index 000000000..e7c026883
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table/body-test.js
@@ -0,0 +1,36 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, find } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+
+module('Integration | Component | rose/table/body', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('tbody'));
+ assert.ok(find('.rose-table-body'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#body'));
+ });
+
+ test('it renders with row', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('tr'));
+ });
+
+ test('it renders with row cell', async function (assert) {
+ await render(hbs`
+
+
+
+ `);
+ assert.ok(find('td'));
+ assert.ok(find('.rose-table-body-cell'));
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/table/cell-test.js b/addons/rose/tests/integration/components/rose/table/cell-test.js
new file mode 100644
index 000000000..b4dfb8de8
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table/cell-test.js
@@ -0,0 +1,26 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, find } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+
+module('Integration | Component | rose/table/cell', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('td'));
+ assert.ok(find('.rose-table-cell'));
+ assert.ok(find('.rose-table-body-cell'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#cell'));
+ });
+
+ test('it renders as header cell', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('.rose-table-cell'));
+ assert.ok(find('.rose-table-header-cell'));
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/table/header-test.js b/addons/rose/tests/integration/components/rose/table/header-test.js
new file mode 100644
index 000000000..9c1c7c795
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table/header-test.js
@@ -0,0 +1,36 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, find } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+
+module('Integration | Component | rose/table/header', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('thead'));
+ assert.ok(find('.rose-table-header'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#header'));
+ });
+
+ test('it renders with row', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('tr'));
+ });
+
+ test('it renders with row cell', async function (assert) {
+ await render(hbs`
+
+
+
+ `);
+ assert.ok(find('th'));
+ assert.ok(find('.rose-table-header-cell'));
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/table/row-test.js b/addons/rose/tests/integration/components/rose/table/row-test.js
new file mode 100644
index 000000000..efb3182d4
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table/row-test.js
@@ -0,0 +1,26 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, find } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+
+module('Integration | Component | rose/table/row', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('tr'));
+ assert.ok(find('.rose-table-row'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#row'));
+ });
+
+ test('it renders with cell', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('.rose-table-cell'));
+ });
+});