diff --git a/elements/howto-menu-button/README.md b/elements/howto-menu-button/README.md
new file mode 100644
index 00000000..6d20020a
--- /dev/null
+++ b/elements/howto-menu-button/README.md
@@ -0,0 +1,4 @@
+A menu button is a button that opens a menu.
+It is referenced by the menu using aria-labelledby attribute.
+
+See: https://www.w3.org/TR/wai-aria-practices-1.1/#menubutton
diff --git a/elements/howto-menu-button/demo.html b/elements/howto-menu-button/demo.html
new file mode 100644
index 00000000..096637de
--- /dev/null
+++ b/elements/howto-menu-button/demo.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+ - option 1
+ - option 2
+ - option 3
+
+
diff --git a/elements/howto-menu-button/howto-menu-button.e2etest.js b/elements/howto-menu-button/howto-menu-button.e2etest.js
new file mode 100644
index 00000000..c6e84d67
--- /dev/null
+++ b/elements/howto-menu-button/howto-menu-button.e2etest.js
@@ -0,0 +1,62 @@
+const helper = require('../../tools/selenium-helper.js');
+const expect = require('chai').expect;
+const {Key} = require('selenium-webdriver');
+
+describe('howto-menu-button', function() {
+ let success;
+ let driver;
+ beforeEach(function() {
+ driver = this.driver;
+ return this.driver.get(`${this.address}/howto-menu-button/demo.html`)
+ .then(_ => helper.waitForElement(this.driver, 'howto-menu-button'));
+ });
+
+ function _focusMenuBtn() {
+ return driver.executeScript(_ => {
+ window.menuBtn = document.querySelector('howto-menu-button');
+ window.menuBtn.focus();
+ });
+ }
+
+ async function _assessFirstItemFocused() {
+ success = await driver.executeScript(`
+ return document.activeElement ===
+ document.querySelector('[role="menuitem"]');
+ `);
+ expect(success).to.equal(true);
+ }
+
+ async function _assessMenuOpened() {
+ success = await driver.executeScript(`
+ const menu = document.querySelector('[aria-labelledby="menu-btn"]');
+ return menu.getAttribute('aria-hidden') === 'false';
+ `);
+ expect(success).to.equal(true);
+ }
+
+ let tests = [
+ {key: 'ARROW_DOWN'},
+ {key: 'ENTER'},
+ {key: 'SPACE'},
+ ];
+
+ tests.forEach(function(test) {
+ it('should open the menu on [' + test.key + ']',
+ async function() {
+ await _focusMenuBtn();
+ await this.driver.actions().sendKeys(Key[test.key]).perform();
+ await _assessMenuOpened();
+ }
+ );
+ });
+
+ tests.forEach(function(test) {
+ it('should focus the first menu item on [' + test.key + ']',
+ async function() {
+ await _focusMenuBtn();
+ await this.driver.actions().sendKeys(Key[test.key]).perform();
+ await _assessFirstItemFocused();
+ }
+ );
+ });
+});
diff --git a/elements/howto-menu-button/howto-menu-button.js b/elements/howto-menu-button/howto-menu-button.js
new file mode 100644
index 00000000..81dbc02c
--- /dev/null
+++ b/elements/howto-menu-button/howto-menu-button.js
@@ -0,0 +1,82 @@
+(function() {
+ /**
+ * Define key codes to help with handling keyboard events.
+ */
+ const KEYCODE = {
+ DOWN: 40,
+ ENTER: 13,
+ SPACE: 32,
+ };
+
+ class HowtoMenuButton extends HTMLElement {
+ /**
+ * A getter for the first menuitem in the menu.
+ */
+ get firstMenuItem() {
+ return this._menu.querySelector('[role^="menuitem"]:first-of-type');
+ }
+
+ /**
+ * Returns true if the menu is currently opened.
+ */
+ _isMenuOpen() {
+ return !(this._menu.getAttribute('aria-hidden') === 'true');
+ }
+
+ /**
+ * Opens the menu if it was closed and vice versa.
+ */
+ _toggleMenu() {
+ const isOpen = this._isMenuOpen();
+ this._menu.setAttribute('aria-hidden', isOpen);
+ if (!isOpen) {
+ // Set focus on first menuitem.
+ this.firstMenuItem && this.firstMenuItem.focus();
+ }
+ }
+
+ /**
+ * Controls keyboard interactions.
+ */
+ _handleKeydown(e) {
+ const triggers = [KEYCODE.DOWN, KEYCODE.ENTER, KEYCODE.SPACE];
+ if (triggers.indexOf(e.keyCode) > -1) {
+ this._toggleMenu();
+ }
+ }
+
+ /**
+ * Sets up the menu button element.
+ */
+ connectedCallback() {
+ this.setAttribute('role', 'button');
+ this.setAttribute('aria-label', 'menu');
+ this.setAttribute('aria-haspopup', true);
+ this.setAttribute('tabindex', 0);
+
+ this.style.display = 'inline-block';
+ this.style.width = parseInt(this.getAttribute('width'), 10) + 'px';
+ this.style.height = parseInt(this.getAttribute('height'), 10) + 'px';
+ this.style.overflow = 'hidden';
+ this.style.color = 'transparent';
+ this.style.background = ('linear-gradient(to bottom, black, black 20%,' +
+ ' white 20%, white 40%, black 40%, black 60%, white 60%, white 80%,' +
+ ' black 80%, black 100%)');
+
+ this._menu = document.querySelector('[aria-labelledby=' + this.id + ']');
+ // TODO: Should this relationship use labelledby or aria-controls?
+ this.addEventListener('click', this._toggleMenu);
+ this.addEventListener('keydown', this._handleKeydown);
+ }
+
+ /**
+ * Unregisters the event listeners that were set up in connectedCallback.
+ */
+ disconnectedCallback() {
+ this.removeEventListener('click', this._toggleMenu);
+ this.removeEventListener('keydown', this._handleKeydown);
+ }
+ }
+
+ window.customElements.define('howto-menu-button', HowtoMenuButton);
+})();