diff --git a/addons/rose/addon/components/rose/dropdown.js b/addons/rose/addon/components/rose/dropdown.js
new file mode 100644
index 000000000..76723d8bf
--- /dev/null
+++ b/addons/rose/addon/components/rose/dropdown.js
@@ -0,0 +1,26 @@
+import Component from '@ember/component';
+import layout from '../../templates/components/rose/dropdown';
+import { action } from '@ember/object';
+
+export default Component.extend({
+
+ // =attributes
+
+ layout,
+ tagName: '',
+
+ // =actions
+
+ /**
+ * Closes the details element by setting its `open` attribute to `false`.
+ * While the details element handles open/close functionality without
+ * JavaScript, we include this helper action to ensure that when the user
+ * clicks _outside_ of the element, it still closes.
+ * @param {HTMLElement} element
+ */
+ @action
+ close(element) {
+ if (element.open) element.open = false;
+ },
+
+});
diff --git a/addons/rose/addon/components/rose/dropdown.stories.mdx b/addons/rose/addon/components/rose/dropdown.stories.mdx
new file mode 100644
index 000000000..87c50f1f1
--- /dev/null
+++ b/addons/rose/addon/components/rose/dropdown.stories.mdx
@@ -0,0 +1,45 @@
+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';
+
+
+
+# Dropdown
+
+A simple dropdown component with support for links and buttons.
+
+
+ {{
+ template: hbs`
+
+ Menu item
+ Menu item
+ Button menu item
+
+ `,
+ context: {}
+ }}
+
+
+
+ {{
+ template: hbs`
+
+ Menu item
+ Menu item
+ Button menu item
+
+ `,
+ context: {}
+ }}
+
+
+
+```htmlbars
+
+ Menu item
+ Menu item
+ Button menu item
+
+```
diff --git a/addons/rose/addon/styles/rose/components/_dropdown.scss b/addons/rose/addon/styles/rose/components/_dropdown.scss
new file mode 100644
index 000000000..bd87f3acb
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/_dropdown.scss
@@ -0,0 +1,111 @@
+@use '../variables/sizing';
+@use '../utilities/type';
+@use '../utilities/dropdown';
+
+.rose-dropdown {
+ position: relative;
+ display: inline-block;
+
+ .rose-dropdown-trigger {
+ @include type.type(button, semibold);
+ cursor: default;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ margin-right: sizing.rems(s);
+ padding: 0 sizing.rems(xl) 0 sizing.rems(l);
+ $lineHeight: 36 / 13;
+ line-height: $lineHeight;
+
+ //color: var(--subtle);
+ background: url("data:image/svg+xml;utf-8,")
+ no-repeat right sizing.rems(s) center;
+ background-size: sizing.rems(m);
+
+ &:hover {
+ background-color: var(--ui-gray-subtler-5);
+ }
+
+ &:not(.has-icon-only) .rose-icon:first-child {
+ margin-left: -(sizing.rems(xs));
+ margin-right: sizing.rems(xxs);
+ }
+
+ &.has-icon-only {
+ padding-right: sizing.rems(l);
+ padding-left: sizing.rems(s);
+ background-position: right sizing.rems(xs) center;
+
+ .rose-dropdown-trigger-text {
+ position: absolute;
+ top: -1000px;
+ left: -1000px;
+ width: 1px;
+ height: 1px;
+ opacity: 0;
+ }
+ }
+
+ // Hide default twistie triangle
+ // Webkit / Chromium
+ &::-webkit-details-marker {
+ display: none;
+ }
+ // Firefox
+ list-style-type: none;
+ &::marker {
+ display: none;
+ }
+ }
+
+ .rose-dropdown-trigger:active,
+ &[open] > .rose-dropdown-trigger {
+ background-color: var(--ui-gray-subtler-4);
+ }
+
+ .rose-dropdown-content {
+ $xs: sizing.rems(xs); // 8
+ $xxs: sizing.rems(xxs); // 4
+ position: absolute;
+ padding: $xxs 0;
+ border: 1px solid var(--ui-gray-subtler-2);
+ border-radius: sizing.rems(xxxs);
+ background: var(--subtle);
+ box-shadow: 0 $xxs $xxs rgba(var(--stark-components), var(--opacity-3)),
+ 0 $xxs $xs rgba(var(--stark-components), var(--opacity-4));
+ z-index: 99;
+ }
+
+ &.dropdown-right {
+ .rose-dropdown-content {
+ right: 0;
+ }
+ }
+
+ .rose-dropdown-link,
+ .rose-button-dropdown {
+ @include type.type(m, normal);
+ display: block;
+ // Targeting min-width of 200
+ min-width: (sizing.rems(m) + sizing.rems(xxs)) * 10;
+ padding: 0 sizing.rems(s);
+ margin: 0;
+ border: 0;
+ color: var(--stark);
+ background-color: transparent;
+ // target line height is 36, which is not a standard line height for this
+ // font size
+ $lineHeight: 36 / 16;
+ line-height: $lineHeight;
+ text-align: left;
+ text-decoration: none;
+
+ &:hover {
+ background-color: var(--ui-gray-subtler-4);
+ }
+ }
+
+ .rose-button-dropdown {
+ width: 100%;
+ }
+}
diff --git a/addons/rose/addon/styles/rose/components/_index.scss b/addons/rose/addon/styles/rose/components/_index.scss
index 66b1f3043..6c0e6f1df 100644
--- a/addons/rose/addon/styles/rose/components/_index.scss
+++ b/addons/rose/addon/styles/rose/components/_index.scss
@@ -1,4 +1,5 @@
@import 'button';
+@import 'dropdown';
@import 'form';
@import 'header';
@import 'layout';
diff --git a/addons/rose/addon/templates/components/rose/dropdown.hbs b/addons/rose/addon/templates/components/rose/dropdown.hbs
new file mode 100644
index 000000000..51b429435
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/dropdown.hbs
@@ -0,0 +1,32 @@
+
+
+
+
+ {{#if @icon}}
+
+ {{/if}}
+
+
+ {{@text}}
+
+
+
+
+
+ {{yield (hash
+ link=(component "link-to" class="rose-dropdown-link")
+ button=(component "rose/button" style="dropdown")
+ )}}
+
+
+
diff --git a/addons/rose/app/components/rose/dropdown.js b/addons/rose/app/components/rose/dropdown.js
new file mode 100644
index 000000000..f51c98374
--- /dev/null
+++ b/addons/rose/app/components/rose/dropdown.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/dropdown';
\ No newline at end of file
diff --git a/addons/rose/tests/dummy/app/templates/playground.hbs b/addons/rose/tests/dummy/app/templates/playground.hbs
index 5f02a47ef..8f7d026b4 100644
--- a/addons/rose/tests/dummy/app/templates/playground.hbs
+++ b/addons/rose/tests/dummy/app/templates/playground.hbs
@@ -50,6 +50,12 @@
Dummy app index template
+
+ Menu item
+ Menu item
+ Button menu item
+
+
`);
+ assert.ok(find('.rose-dropdown'));
+ });
+
+ test('it renders with html attributes', async function (assert) {
+ await render(
+ hbs``
+ );
+ assert.ok(find('#custom-id'));
+ assert.ok(find('.custom-class'));
+ });
+
+ test('it renders with trigger', async function (assert) {
+ await render(hbs``);
+ assert.equal(
+ find('.rose-dropdown-trigger').textContent.trim(),
+ 'Click me'
+ );
+ });
+
+ test('it supports an icon', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('svg'));
+ });
+
+ test('it supports icon-only display', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('.has-icon-only'));
+ });
+
+ test('it renders with content', async function (assert) {
+ await render(hbs`
+
+
+
+
+
+ `);
+ assert.ok(find('.rose-dropdown-content'));
+ assert.equal(findAll('a').length, 2);
+ assert.equal(findAll('button').length, 3);
+ });
+
+ test('it is toggled on click on trigger content', async function (assert) {
+ await render(hbs``);
+
+ assert.notOk(find('#dropdown').open);
+ await click('summary');
+ assert.ok(find('#dropdown').open);
+ await click('summary');
+ assert.notOk(find('#dropdown').open);
+ });
+
+ test('it is closed with outside click is triggered', async function (assert) {
+ await render(hbs`
+
+
+
+ `);
+
+ await click('summary');
+ assert.ok(find('#dropdown').open);
+ await click('#wrapper');
+ assert.notOk(find('#dropdown').open);
+ });
+});