diff --git a/src/panels/lovelace/common/compute-notifications.js b/src/panels/lovelace/common/compute-notifications.js
new file mode 100644
index 0000000000..802805cebb
--- /dev/null
+++ b/src/panels/lovelace/common/compute-notifications.js
@@ -0,0 +1,12 @@
+import computeDomain from '../../../common/entity/compute_domain.js';
+
+const NOTIFICATION_DOMAINS = [
+ 'configurator',
+ 'persistent_notification'
+];
+
+export default function computeNotifications(states) {
+ return Object.keys(states)
+ .filter(entityId => NOTIFICATION_DOMAINS.includes(computeDomain(entityId)))
+ .map(entityId => states[entityId]);
+}
diff --git a/src/panels/lovelace/components/notifications/hui-configurator-notification-item.js b/src/panels/lovelace/components/notifications/hui-configurator-notification-item.js
new file mode 100644
index 0000000000..25c2612bf5
--- /dev/null
+++ b/src/panels/lovelace/components/notifications/hui-configurator-notification-item.js
@@ -0,0 +1,53 @@
+import '@polymer/paper-button/paper-button.js';
+import '@polymer/paper-icon-button/paper-icon-button.js';
+
+import { html } from '@polymer/polymer/lib/utils/html-tag.js';
+import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+
+import './hui-notification-item-template.js';
+
+import EventsMixin from '../../../../mixins/events-mixin.js';
+import LocalizeMixin from '../../../../mixins/localize-mixin.js';
+
+/*
+ * @appliesMixin EventsMixin
+ * @appliesMixin LocalizeMixin
+ */
+export class HuiConfiguratorNotificationItem extends EventsMixin(LocalizeMixin(PolymerElement)) {
+ static get template() {
+ return html`
+
+ [[localize('domain.configurator')]]
+
+ [[_getMessage(stateObj)]]
+
+ [[_localizeState(stateObj.state)]]
+
+ `;
+ }
+
+ static get properties() {
+ return {
+ hass: Object,
+ stateObj: Object
+ };
+ }
+
+ _handleClick() {
+ this.fire('hass-more-info', { entityId: this.stateObj.entity_id });
+ }
+
+ _localizeState(state) {
+ return this.localize(`state.configurator.${state}`);
+ }
+
+ _getMessage(stateObj) {
+ const friendlyName = stateObj.attributes.friendly_name;
+ return this.localize('ui.notification_drawer.click_to_configure', 'entity', friendlyName);
+ }
+}
+customElements.define('hui-configurator-notification-item', HuiConfiguratorNotificationItem);
diff --git a/src/panels/lovelace/components/notifications/hui-notification-drawer.js b/src/panels/lovelace/components/notifications/hui-notification-drawer.js
new file mode 100644
index 0000000000..d641b280ba
--- /dev/null
+++ b/src/panels/lovelace/components/notifications/hui-notification-drawer.js
@@ -0,0 +1,179 @@
+import '@polymer/paper-button/paper-button.js';
+import '@polymer/paper-icon-button/paper-icon-button.js';
+import '@polymer/app-layout/app-toolbar/app-toolbar.js';
+
+import { html } from '@polymer/polymer/lib/utils/html-tag.js';
+import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+
+import './hui-notification-item.js';
+
+import computeNotifications from '../../common/compute-notifications.js';
+
+import EventsMixin from '../../../../mixins/events-mixin.js';
+import LocalizeMixin from '../../../../mixins/localize-mixin.js';
+
+/*
+ * @appliesMixin EventsMixin
+ * @appliesMixin LocalizeMixin
+ */
+export class HuiNotificationDrawer extends EventsMixin(LocalizeMixin(PolymerElement)) {
+ static get template() {
+ return html`
+
+
+
+
+ [[localize('ui.notification_drawer.title')]]
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [[localize('ui.notification_drawer.empty')]]
+
+
+
+ `;
+ }
+
+ static get properties() {
+ return {
+ hass: Object,
+ _entities: {
+ type: Array,
+ computed: '_getEntities(hass.states, hidden)'
+ },
+ narrow: {
+ type: Boolean,
+ reflectToAttribute: true
+ },
+ open: {
+ type: Boolean,
+ notify: true,
+ observer: '_openChanged'
+ },
+ hidden: {
+ type: Boolean,
+ value: true,
+ reflectToAttribute: true
+ }
+ };
+ }
+
+ _getEntities(states, hidden) {
+ return (states && !hidden) ? computeNotifications(states) : [];
+ }
+
+ _closeDrawer(ev) {
+ ev.stopPropagation();
+ this.open = false;
+ }
+
+ _empty(entities) {
+ return entities.length === 0;
+ }
+
+ _openChanged(open) {
+ clearTimeout(this._openTimer);
+ if (open) {
+ // Render closed then animate open
+ this.hidden = false;
+ this._openTimer = setTimeout(() => {
+ this.classList.add('open');
+ }, 50);
+ } else {
+ // Animate closed then hide
+ this.classList.remove('open');
+ this._openTimer = setTimeout(() => {
+ this.hidden = true;
+ }, 250);
+ }
+ }
+}
+customElements.define('hui-notification-drawer', HuiNotificationDrawer);
diff --git a/src/panels/lovelace/components/notifications/hui-notification-item-template.js b/src/panels/lovelace/components/notifications/hui-notification-item-template.js
new file mode 100644
index 0000000000..a4188778db
--- /dev/null
+++ b/src/panels/lovelace/components/notifications/hui-notification-item-template.js
@@ -0,0 +1,46 @@
+import '@polymer/paper-button/paper-button.js';
+import '@polymer/paper-icon-button/paper-icon-button.js';
+
+import { html } from '@polymer/polymer/lib/utils/html-tag.js';
+import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+
+import '../../../../components/ha-card.js';
+
+export class HuiNotificationItemTemplate extends PolymerElement {
+ static get template() {
+ return html`
+
+
+
+
+
+
+
+
+
+
+ `;
+ }
+}
+customElements.define('hui-notification-item-template', HuiNotificationItemTemplate);
diff --git a/src/panels/lovelace/components/notifications/hui-notification-item.js b/src/panels/lovelace/components/notifications/hui-notification-item.js
new file mode 100644
index 0000000000..9fcab42428
--- /dev/null
+++ b/src/panels/lovelace/components/notifications/hui-notification-item.js
@@ -0,0 +1,33 @@
+import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+import computeDomain from '../../../../common/entity/compute_domain.js';
+
+import './hui-configurator-notification-item.js';
+import './hui-persistent-notification-item.js';
+
+export class HuiNotificationItem extends PolymerElement {
+ static get properties() {
+ return {
+ hass: Object,
+ stateObj: {
+ type: Object,
+ observer: '_stateChanged'
+ }
+ };
+ }
+
+ _stateChanged(stateObj) {
+ if (this.lastChild) {
+ this.removeChild(this.lastChild);
+ }
+
+ if (!stateObj) return;
+
+ const domain = computeDomain(stateObj.entity_id);
+ const tag = `hui-${domain}-notification-item`;
+ const el = document.createElement(tag);
+ el.hass = this.hass;
+ el.stateObj = stateObj;
+ this.appendChild(el);
+ }
+}
+customElements.define('hui-notification-item', HuiNotificationItem);
diff --git a/src/panels/lovelace/components/notifications/hui-notifications-button.js b/src/panels/lovelace/components/notifications/hui-notifications-button.js
new file mode 100644
index 0000000000..5d9048cb64
--- /dev/null
+++ b/src/panels/lovelace/components/notifications/hui-notifications-button.js
@@ -0,0 +1,61 @@
+import '@polymer/paper-button/paper-button.js';
+import '@polymer/paper-icon-button/paper-icon-button.js';
+import '@polymer/app-layout/app-toolbar/app-toolbar.js';
+
+import { html } from '@polymer/polymer/lib/utils/html-tag.js';
+import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+
+import computeNotifications from '../../common/compute-notifications.js';
+
+import EventsMixin from '../../../../mixins/events-mixin.js';
+
+/*
+ * @appliesMixin EventsMixin
+ */
+export class HuiNotificationsButton extends EventsMixin(PolymerElement) {
+ static get template() {
+ return html`
+
+
+
+ `;
+ }
+
+ static get properties() {
+ return {
+ hass: Object,
+ notificationsOpen: {
+ type: Boolean,
+ notify: true
+ }
+ };
+ }
+
+ _clicked() {
+ this.notificationsOpen = true;
+ }
+
+ _hasNotifications(states) {
+ return computeNotifications(states).length > 0;
+ }
+}
+customElements.define('hui-notifications-button', HuiNotificationsButton);
diff --git a/src/panels/lovelace/components/notifications/hui-persistent-notification-item.js b/src/panels/lovelace/components/notifications/hui-persistent-notification-item.js
new file mode 100644
index 0000000000..dfe511b9c3
--- /dev/null
+++ b/src/panels/lovelace/components/notifications/hui-persistent-notification-item.js
@@ -0,0 +1,52 @@
+import '@polymer/paper-button/paper-button.js';
+import '@polymer/paper-icon-button/paper-icon-button.js';
+
+import { html } from '@polymer/polymer/lib/utils/html-tag.js';
+import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+
+import computeStateName from '../../../../common/entity/compute_state_name.js';
+
+import '../../../../components/ha-markdown.js';
+import './hui-notification-item-template.js';
+
+import LocalizeMixin from '../../../../mixins/localize-mixin.js';
+
+/*
+ * @appliesMixin LocalizeMixin
+ */
+export class HuiPersistentNotificationItem extends LocalizeMixin(PolymerElement) {
+ static get template() {
+ return html`
+
+ [[_computeTitle(stateObj)]]
+
+
+
+ [[localize('ui.card.persistent_notification.dismiss')]]
+
+ `;
+ }
+
+ static get properties() {
+ return {
+ hass: Object,
+ stateObj: Object
+ };
+ }
+
+ _handleDismiss() {
+ this.hass.callApi('DELETE', `states/${this.stateObj.entity_id}`);
+ }
+
+ _computeTitle(stateObj) {
+ return (stateObj.attributes.title || computeStateName(stateObj));
+ }
+}
+customElements.define(
+ 'hui-persistent_notification-notification-item',
+ HuiPersistentNotificationItem
+);
diff --git a/src/panels/lovelace/hui-root.js b/src/panels/lovelace/hui-root.js
index 2679bf85bb..f4374f0344 100644
--- a/src/panels/lovelace/hui-root.js
+++ b/src/panels/lovelace/hui-root.js
@@ -22,6 +22,8 @@ import '../../layouts/ha-app-layout.js';
import '../../components/ha-start-voice-button.js';
import '../../components/ha-icon.js';
import { loadModule, loadCSS, loadJS } from '../../common/dom/load_resource.js';
+import './components/notifications/hui-notification-drawer.js';
+import './components/notifications/hui-notifications-button.js';
import './hui-unused-entities.js';
import './hui-view.js';
import debounce from '../../common/util/debounce.js';
@@ -72,11 +74,20 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
}
+
[[_computeTitle(config)]]
+