diff --git a/addons/rose/addon/components/rose/notification.js b/addons/rose/addon/components/rose/notification.js
new file mode 100644
index 000000000..0ff646e2d
--- /dev/null
+++ b/addons/rose/addon/components/rose/notification.js
@@ -0,0 +1,38 @@
+import Component from '@ember/component';
+import layout from '../../templates/components/rose/notification';
+import { computed } from '@ember/object';
+
+/**
+ * An accessible alert notification component that displays an icon,
+ * a heading, and optional block content. Notifications may be static or
+ * dismissible. To make a notification dismissible, pass in a
+ * `@dismiss` function.
+ */
+export default Component.extend({
+ layout,
+ tagName: '',
+
+ /**
+ * Returns an icon name based on the notification style.
+ * @type {string}
+ */
+ @computed('style')
+ get icon() {
+ let icon = 'info-circle-fill';
+
+ switch(this.style) {
+ case 'failure':
+ icon = 'cancel-square-fill';
+ break;
+ case 'success':
+ icon = 'check-circle-fill';
+ break;
+ case 'warning':
+ icon = 'alert-triangle';
+ break;
+ }
+
+ return icon;
+ }
+
+});
diff --git a/addons/rose/addon/components/rose/notification.stories.mdx b/addons/rose/addon/components/rose/notification.stories.mdx
new file mode 100644
index 000000000..04cd3f533
--- /dev/null
+++ b/addons/rose/addon/components/rose/notification.stories.mdx
@@ -0,0 +1,84 @@
+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';
+
+
+
+# Notification
+
+Notifications come in four semantic styles: `info` (default), `success`,
+`warning`, and `failure`. Heading text is required. Additional block content
+is optional.
+
+Notifications may be made dismissible by passing a `@dismiss` function into
+the component. Dismissible notifications _must_ also have `@dismissText`.
+This component does not handle dismissal though. Be sure your `@dismiss`
+handler takes appropriate action and hides the notification.
+
+```htmlbars
+
+ An error occurred.
+
+```
+
+
+ {{
+ template: hbs`
+
+ Some useful information.
+
+ `,
+ context: {}
+ }}
+
+
+
+ {{
+ template: hbs`
+
+ Some useful information.
+
+
+ Good job!
+
+
+ Careful now.
+
+
+ An error occurred.
+
+ `,
+ context: {
+ dismiss: action('dismiss'),
+ }
+ }}
+
diff --git a/addons/rose/addon/styles/rose/components/_index.scss b/addons/rose/addon/styles/rose/components/_index.scss
index 475417601..e1518b033 100644
--- a/addons/rose/addon/styles/rose/components/_index.scss
+++ b/addons/rose/addon/styles/rose/components/_index.scss
@@ -1,3 +1,4 @@
@import 'button';
@import 'form';
@import 'header';
+@import 'notification';
diff --git a/addons/rose/addon/styles/rose/components/_notification.scss b/addons/rose/addon/styles/rose/components/_notification.scss
new file mode 100644
index 000000000..71de0adf0
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/_notification.scss
@@ -0,0 +1,105 @@
+@use '../variables/sizing';
+@use '../utilities/type';
+
+.rose-notification {
+ $s: sizing.rems(s); // 12
+ $xs: sizing.rems(xs); // 8
+ $xxs: sizing.rems(xxs); // 4
+ $xxxs: sizing.rems(xxxs); // 2
+ $xxxxs: sizing.rems(xxxxs); // 1
+
+ position: relative;
+ padding: sizing.rems(m);
+ margin-bottom: sizing.rems(m);
+ border: sizing.rems(xxxxs) solid;
+ border-radius: $xxxs;
+ box-shadow: 0 $xs $s rgba(var(--stark-components), var(--opacity-3)),
+ 0 $xxs $xxs rgba(var(--stark-components), var(--opacity-4));
+
+ .rose-notification-icon {
+ position: relative;
+ top: $xxxxs;
+ }
+
+ .rose-notification-header {
+ @include type.type(m, semibold);
+ }
+
+ .rose-notification-body {
+ @include type.type(s);
+ // hack alert, because the icons are still hard to work with
+ margin-left: sizing.rems(m) + $xxs;
+ }
+
+ .rose-notification-dismiss {
+ position: absolute;
+ color: currentColor;
+ background-color: transparent;
+ border-radius: $xxxs;
+ border: 0;
+ padding: 0;
+ cursor: pointer;
+
+ top: sizing.rems(s);
+ right: sizing.rems(s);
+
+ .rose-icon {
+ display: block;
+ padding: $xxs;
+ }
+
+ .rose-notification-dismiss-text {
+ position: absolute;
+ top: -1000px;
+ left: -1000px;
+ width: 1px;
+ height: 1px;
+ opacity: 0;
+ }
+
+ &:hover {
+ background-color: rgba(var(--stark-components), var(--opacity-3))
+ }
+
+ &:active {
+ background-color: rgba(var(--stark-components), var(--opacity-2))
+ }
+ }
+
+ --icon-color: var(--info);
+ --header-color: var(--info-starker-1);
+ --border-color: var(--info-subtler-1);
+ --background-color: var(--info-subtler-3);
+
+ border-color: var(--border-color);
+ background-color: var(--background-color);
+
+ .rose-notification-icon {
+ color: var(--icon-color);
+ }
+
+ .rose-notification-header {
+ color: var(--header-color);
+ }
+
+ &.is-failure {
+ --icon-color: var(--failure);
+ --header-color: var(--failure);
+ --border-color: var(--failure-subtler-1);
+ --background-color: var(--failure-subtler-2);
+ }
+
+ &.is-success {
+ --icon-color: var(--success);
+ --header-color: var(--success-starker-1);
+ --border-color: var(--success-subtler-1);
+ --background-color: var(--success-subtler-2);
+ }
+
+ &.is-warning {
+ --icon-color: var(--warning);
+ --header-color: var(--warning-starker-1);
+ --border-color: var(--warning-subtler-1);
+ --background-color: var(--warning-subtler-2);
+ }
+}
diff --git a/addons/rose/addon/templates/components/rose/notification.hbs b/addons/rose/addon/templates/components/rose/notification.hbs
new file mode 100644
index 000000000..04d60df5f
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/notification.hbs
@@ -0,0 +1,37 @@
+
+
+
+
+
+ {{yield}}
+
+
+ {{#if @dismiss}}
+
+ {{/if}}
+
+
diff --git a/addons/rose/app/components/rose/notification.js b/addons/rose/app/components/rose/notification.js
new file mode 100644
index 000000000..228bd468c
--- /dev/null
+++ b/addons/rose/app/components/rose/notification.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/notification';
\ No newline at end of file
diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs
index 70c9d1747..12fac556c 100644
--- a/addons/rose/tests/dummy/app/templates/index.hbs
+++ b/addons/rose/tests/dummy/app/templates/index.hbs
@@ -30,6 +30,15 @@
Dummy app index template
+
+ An error occurred.
+
+
Icon
diff --git a/addons/rose/tests/integration/components/rose/notification-test.js b/addons/rose/tests/integration/components/rose/notification-test.js
new file mode 100644
index 000000000..6027644d0
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/notification-test.js
@@ -0,0 +1,33 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, find, click } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+
+module('Integration | Component | rose/notification', function(hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function(assert) {
+ await render(hbs`
+
+ An error occurred.
+
+ `);
+ assert.ok(find('.rose-notification'));
+ assert.equal(find('.rose-notification-body').textContent.trim(), 'An error occurred.');
+ });
+
+ test('it has optional dismiss button which triggers a function', async function(assert) {
+ assert.expect(1);
+ this.dismiss = () => assert.ok(true, 'dismiss clicked');
+ await render(hbs`
+
+ `);
+ await click('.rose-notification-dismiss');
+ });
+});