diff --git a/src/components/entity/state-badge.js b/src/components/entity/state-badge.js
deleted file mode 100644
index ab3e0906ab..0000000000
--- a/src/components/entity/state-badge.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import { html } from "@polymer/polymer/lib/utils/html-tag";
-import { PolymerElement } from "@polymer/polymer/polymer-element";
-
-import "../ha-icon";
-import computeStateDomain from "../../common/entity/compute_state_domain";
-import stateIcon from "../../common/entity/state_icon";
-
-class StateBadge extends PolymerElement {
- static get template() {
- return html`
-
-
-
- `;
- }
-
- static get properties() {
- return {
- stateObj: {
- type: Object,
- observer: "_updateIconAppearance",
- },
- overrideIcon: String,
- };
- }
-
- _computeDomain(stateObj) {
- return computeStateDomain(stateObj);
- }
-
- _computeIcon(stateObj, overrideIcon) {
- return overrideIcon || stateIcon(stateObj);
- }
-
- _updateIconAppearance(newVal) {
- var errorMessage = null;
- const iconStyle = {
- color: "",
- filter: "",
- };
- const hostStyle = {
- backgroundImage: "",
- };
- // hide icon if we have entity picture
- if (newVal.attributes.entity_picture) {
- hostStyle.backgroundImage =
- "url(" + newVal.attributes.entity_picture + ")";
- iconStyle.display = "none";
- } else {
- if (newVal.attributes.hs_color) {
- const hue = newVal.attributes.hs_color[0];
- const sat = newVal.attributes.hs_color[1];
- if (sat > 10) iconStyle.color = `hsl(${hue}, 100%, ${100 - sat / 2}%)`;
- }
- if (newVal.attributes.brightness) {
- const brightness = newVal.attributes.brightness;
- if (typeof brightness !== "number") {
- errorMessage = `Type error: state-badge expected number, but type of ${
- newVal.entity_id
- }.attributes.brightness is ${typeof brightness} (${brightness})`;
- // eslint-disable-next-line
- console.warn(errorMessage);
- }
- // lowest brighntess will be around 50% (that's pretty dark)
- iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`;
- }
- }
- Object.assign(this.$.icon.style, iconStyle);
- Object.assign(this.style, hostStyle);
- if (errorMessage) {
- throw new Error(`Frontend error: ${errorMessage}`);
- }
- }
-}
-customElements.define("state-badge", StateBadge);
diff --git a/src/components/entity/state-badge.ts b/src/components/entity/state-badge.ts
new file mode 100644
index 0000000000..4554c382fb
--- /dev/null
+++ b/src/components/entity/state-badge.ts
@@ -0,0 +1,127 @@
+import {
+ LitElement,
+ TemplateResult,
+ css,
+ CSSResult,
+ html,
+ property,
+ PropertyValues,
+ query,
+} from "lit-element";
+import "../ha-icon";
+import computeStateDomain from "../../common/entity/compute_state_domain";
+import stateIcon from "../../common/entity/state_icon";
+import { HassEntity } from "home-assistant-js-websocket";
+// Not duplicate, this is for typing.
+// tslint:disable-next-line
+import { HaIcon } from "../ha-icon";
+
+class StateBadge extends LitElement {
+ @property() public stateObj?: HassEntity;
+ @property() public overrideIcon?: string;
+ @query("ha-icon") private _icon!: HaIcon;
+
+ protected render(): TemplateResult | void {
+ const stateObj = this.stateObj;
+
+ if (!stateObj) {
+ return html``;
+ }
+
+ return html`
+
+ `;
+ }
+
+ protected updated(changedProps: PropertyValues) {
+ if (!changedProps.has("stateObj")) {
+ return;
+ }
+ const stateObj = this.stateObj;
+
+ const iconStyle: Partial = {
+ color: "",
+ filter: "",
+ };
+ const hostStyle: Partial = {
+ backgroundImage: "",
+ };
+ if (stateObj) {
+ // hide icon if we have entity picture
+ if (stateObj.attributes.entity_picture) {
+ hostStyle.backgroundImage =
+ "url(" + stateObj.attributes.entity_picture + ")";
+ iconStyle.display = "none";
+ } else {
+ if (stateObj.attributes.hs_color) {
+ const hue = stateObj.attributes.hs_color[0];
+ const sat = stateObj.attributes.hs_color[1];
+ if (sat > 10) {
+ iconStyle.color = `hsl(${hue}, 100%, ${100 - sat / 2}%)`;
+ }
+ }
+ if (stateObj.attributes.brightness) {
+ const brightness = stateObj.attributes.brightness;
+ if (typeof brightness !== "number") {
+ const errorMessage = `Type error: state-badge expected number, but type of ${
+ stateObj.entity_id
+ }.attributes.brightness is ${typeof brightness} (${brightness})`;
+ // tslint:disable-next-line
+ console.warn(errorMessage);
+ }
+ // lowest brighntess will be around 50% (that's pretty dark)
+ iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`;
+ }
+ }
+ }
+ Object.assign(this._icon.style, iconStyle);
+ Object.assign(this.style, hostStyle);
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ :host {
+ position: relative;
+ display: inline-block;
+ width: 40px;
+ color: var(--paper-item-icon-color, #44739e);
+ border-radius: 50%;
+ height: 40px;
+ text-align: center;
+ background-size: cover;
+ line-height: 40px;
+ }
+
+ ha-icon {
+ transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
+ }
+
+ /* Color the icon if light or sun is on */
+ ha-icon[data-domain="light"][data-state="on"],
+ ha-icon[data-domain="switch"][data-state="on"],
+ ha-icon[data-domain="binary_sensor"][data-state="on"],
+ ha-icon[data-domain="fan"][data-state="on"],
+ ha-icon[data-domain="sun"][data-state="above_horizon"] {
+ color: var(--paper-item-icon-active-color, #fdd835);
+ }
+
+ /* Color the icon if unavailable */
+ ha-icon[data-state="unavailable"] {
+ color: var(--state-icon-unavailable-color);
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "state-badge": StateBadge;
+ }
+}
+
+customElements.define("state-badge", StateBadge);
diff --git a/src/components/ha-icon.js b/src/components/ha-icon.js
deleted file mode 100644
index 29cb8b88b1..0000000000
--- a/src/components/ha-icon.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import "@polymer/iron-icon/iron-icon";
-
-const IronIconClass = customElements.get("iron-icon");
-
-let loaded = false;
-
-class HaIcon extends IronIconClass {
- listen(...args) {
- super.listen(...args);
-
- if (!loaded && this._iconsetName === "mdi") {
- loaded = true;
- import(/* webpackChunkName: "mdi-icons" */ "../resources/mdi-icons");
- }
- }
-}
-
-customElements.define("ha-icon", HaIcon);
diff --git a/src/components/ha-icon.ts b/src/components/ha-icon.ts
new file mode 100644
index 0000000000..2e2a6ba848
--- /dev/null
+++ b/src/components/ha-icon.ts
@@ -0,0 +1,36 @@
+import { Constructor } from "lit-element";
+import "@polymer/iron-icon/iron-icon";
+// Not duplicate, this is for typing.
+// tslint:disable-next-line
+import { IronIconElement } from "@polymer/iron-icon/iron-icon";
+
+const ironIconClass = customElements.get("iron-icon") as Constructor<
+ IronIconElement
+>;
+
+let loaded = false;
+
+export class HaIcon extends ironIconClass {
+ private _iconsetName?: string;
+
+ public listen(
+ node: EventTarget | null,
+ eventName: string,
+ methodName: string
+ ): void {
+ super.listen(node, eventName, methodName);
+
+ if (!loaded && this._iconsetName === "mdi") {
+ loaded = true;
+ import(/* webpackChunkName: "mdi-icons" */ "../resources/mdi-icons");
+ }
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-icon": HaIcon;
+ }
+}
+
+customElements.define("ha-icon", HaIcon);