diff --git a/package.json b/package.json
index a86b58dbedf0f72813f92978b5120294ea8d4c40..123aaec21dbee756dbaa10b5229c8be8f0816315 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,9 @@
     "prettier": "prettier --write src/**/*.js"
   },
   "dependencies": {
+    "@fortawesome/fontawesome": "^1.1.5",
+    "@fortawesome/fontawesome-free-solid": "^5.0.9",
+    "@fortawesome/react-fontawesome": "^0.0.18",
     "aframe-billboard-component": "^1.0.0",
     "aframe-extras": "^3.12.4",
     "aframe-input-mapping-component": "https://github.com/johnshaughnessy/aframe-input-mapping-component#feature/map-to-array",
diff --git a/src/assets/avatars/BotBobo_Avatar.glb b/src/assets/avatars/BotBobo_Avatar.glb
new file mode 100644
index 0000000000000000000000000000000000000000..66862adb28f35fb3bb1ca4954d840a1f9a91bb96
Binary files /dev/null and b/src/assets/avatars/BotBobo_Avatar.glb differ
diff --git a/src/assets/avatars/BotBobo_Avatar_Unlit.glb b/src/assets/avatars/BotBobo_Avatar_Unlit.glb
new file mode 100644
index 0000000000000000000000000000000000000000..e4a939a242fc930dc52c7d60aa145b7b86023330
Binary files /dev/null and b/src/assets/avatars/BotBobo_Avatar_Unlit.glb differ
diff --git a/src/assets/avatars/BotDom_Avatar.glb b/src/assets/avatars/BotDom_Avatar.glb
index 9c72e3d2fc170d6b0782d741eca441a62780daad..098a042ead7b919170c6c4c6a63ba40f62a1b81b 100644
Binary files a/src/assets/avatars/BotDom_Avatar.glb and b/src/assets/avatars/BotDom_Avatar.glb differ
diff --git a/src/assets/avatars/BotDom_Avatar_Unlit.glb b/src/assets/avatars/BotDom_Avatar_Unlit.glb
new file mode 100644
index 0000000000000000000000000000000000000000..88c830ae9f73581683050705ad75f321bdeecb37
Binary files /dev/null and b/src/assets/avatars/BotDom_Avatar_Unlit.glb differ
diff --git a/src/assets/avatars/BotGreg_Avatar.glb b/src/assets/avatars/BotGreg_Avatar.glb
new file mode 100644
index 0000000000000000000000000000000000000000..33ca3ee66249ed267688353ef0ab82fd97dfc8bc
Binary files /dev/null and b/src/assets/avatars/BotGreg_Avatar.glb differ
diff --git a/src/assets/avatars/BotGreg_Avatar_Unlit.glb b/src/assets/avatars/BotGreg_Avatar_Unlit.glb
new file mode 100644
index 0000000000000000000000000000000000000000..ed0a5291790f79029b6c7e624a45253c5f6caeb3
Binary files /dev/null and b/src/assets/avatars/BotGreg_Avatar_Unlit.glb differ
diff --git a/src/assets/avatars/BotGuest_Avatar.glb b/src/assets/avatars/BotGuest_Avatar.glb
new file mode 100644
index 0000000000000000000000000000000000000000..e0be8e9cbad1a1d6167c0c0ccf333285d0857472
Binary files /dev/null and b/src/assets/avatars/BotGuest_Avatar.glb differ
diff --git a/src/assets/avatars/BotGuest_Avatar_Unlit.glb b/src/assets/avatars/BotGuest_Avatar_Unlit.glb
new file mode 100644
index 0000000000000000000000000000000000000000..3bc50b18e3ffbf9f9007a6f7790a8512284949a7
Binary files /dev/null and b/src/assets/avatars/BotGuest_Avatar_Unlit.glb differ
diff --git a/src/assets/avatars/BotJim_Avatar.glb b/src/assets/avatars/BotJim_Avatar.glb
new file mode 100644
index 0000000000000000000000000000000000000000..34c1a8cf274d0ed62b2bc22b51d573f6bafc1728
Binary files /dev/null and b/src/assets/avatars/BotJim_Avatar.glb differ
diff --git a/src/assets/avatars/BotJim_Avatar_Unlit.glb b/src/assets/avatars/BotJim_Avatar_Unlit.glb
new file mode 100644
index 0000000000000000000000000000000000000000..534356f80deb17b8808bc778db2db048811baf6a
Binary files /dev/null and b/src/assets/avatars/BotJim_Avatar_Unlit.glb differ
diff --git a/src/assets/avatars/BotKev_Avatar.glb b/src/assets/avatars/BotKev_Avatar.glb
new file mode 100644
index 0000000000000000000000000000000000000000..a20a54bcc42926a79b623e360a4ed9ddb2380695
Binary files /dev/null and b/src/assets/avatars/BotKev_Avatar.glb differ
diff --git a/src/assets/avatars/BotKev_Avatar_Unlit.glb b/src/assets/avatars/BotKev_Avatar_Unlit.glb
new file mode 100644
index 0000000000000000000000000000000000000000..d70196b7a3f83f25445cef27cf0fbc7493805cd2
Binary files /dev/null and b/src/assets/avatars/BotKev_Avatar_Unlit.glb differ
diff --git a/src/assets/avatars/BotPinky_Avatar.glb b/src/assets/avatars/BotPinky_Avatar.glb
new file mode 100644
index 0000000000000000000000000000000000000000..da14ccd6ada339e7d28c1dfcd636a0810d45907c
Binary files /dev/null and b/src/assets/avatars/BotPinky_Avatar.glb differ
diff --git a/src/assets/avatars/BotPinky_Avatar_Unlit.glb b/src/assets/avatars/BotPinky_Avatar_Unlit.glb
new file mode 100644
index 0000000000000000000000000000000000000000..4029c8c6feb3dfe137d932ff0e7eb9223c60b6d1
Binary files /dev/null and b/src/assets/avatars/BotPinky_Avatar_Unlit.glb differ
diff --git a/src/assets/avatars/BotRobert_Avatar.glb b/src/assets/avatars/BotRobert_Avatar.glb
new file mode 100644
index 0000000000000000000000000000000000000000..68d28e5a19616eae6e5e5c8694182efe02643c65
Binary files /dev/null and b/src/assets/avatars/BotRobert_Avatar.glb differ
diff --git a/src/assets/avatars/BotRobert_Avatar_Unlit.glb b/src/assets/avatars/BotRobert_Avatar_Unlit.glb
new file mode 100644
index 0000000000000000000000000000000000000000..505eae50b5604bdbe3a7076421dabd1847e561f1
Binary files /dev/null and b/src/assets/avatars/BotRobert_Avatar_Unlit.glb differ
diff --git a/src/assets/avatars/BotWoody_Avatar.glb b/src/assets/avatars/BotWoody_Avatar.glb
new file mode 100644
index 0000000000000000000000000000000000000000..0a205ee9311b5c73f96f5fdf562970bd362b1a21
Binary files /dev/null and b/src/assets/avatars/BotWoody_Avatar.glb differ
diff --git a/src/assets/avatars/BotWoody_Avatar_Unlit.glb b/src/assets/avatars/BotWoody_Avatar_Unlit.glb
new file mode 100644
index 0000000000000000000000000000000000000000..bd330d47f269ccaae436fbb3a33aa85b51eee6fd
Binary files /dev/null and b/src/assets/avatars/BotWoody_Avatar_Unlit.glb differ
diff --git a/src/assets/avatars/avatars.json b/src/assets/avatars/avatars.json
new file mode 100644
index 0000000000000000000000000000000000000000..8f04c546fbe55d90507ab92ee2e3e599144d7d29
--- /dev/null
+++ b/src/assets/avatars/avatars.json
@@ -0,0 +1,67 @@
+{
+  "avatars": [
+    {
+      "id": "botdefault",
+      "models": {
+        "low": "BotDefault_Avatar_Unlit.glb",
+        "high": "BotDefault_Avatar.glb"
+      }
+    },
+    {
+      "id": "botbobo",
+      "models": {
+        "low": "BotBobo_Avatar_Unlit.glb",
+        "high": "BotBobo_Avatar.glb"
+      }
+    },
+    {
+      "id": "botdom",
+      "models": {
+        "low": "BotDom_Avatar_Unlit.glb",
+        "high": "BotDom_Avatar.glb"
+      }
+    },
+    {
+      "id": "botgreg",
+      "models": {
+        "low": "BotGreg_Avatar_Unlit.glb",
+        "high": "BotGreg_Avatar.glb"
+      }
+    },
+    {
+      "id": "botguest",
+      "models": {
+        "low": "BotGuest_Avatar_Unlit.glb",
+        "high": "BotGuest_Avatar.glb"
+      }
+    },
+    {
+      "id": "botjim",
+      "models": {
+        "low": "BotJim_Avatar_Unlit.glb",
+        "high": "BotJim_Avatar.glb"
+      }
+    },
+    {
+      "id": "botpinky",
+      "models": {
+        "low": "BotPinky_Avatar_Unlit.glb",
+        "high": "BotPinky_Avatar.glb"
+      }
+    },
+    {
+      "id": "botrobert",
+      "models": {
+        "low": "BotRobert_Avatar_Unlit.glb",
+        "high": "BotRobert_Avatar.glb"
+      }
+    },
+    {
+      "id": "botwoody",
+      "models": {
+        "low": "BotWoody_Avatar_Unlit.glb",
+        "high": "BotWoody_Avatar.glb"
+      }
+    }
+  ]
+}
diff --git a/src/assets/stylesheets/avatar-selector.scss b/src/assets/stylesheets/avatar-selector.scss
new file mode 100644
index 0000000000000000000000000000000000000000..1663ec5b6ba92ef81c1b7fc9955f98dd0788272b
--- /dev/null
+++ b/src/assets/stylesheets/avatar-selector.scss
@@ -0,0 +1,45 @@
+@import 'fonts';
+@import 'shared';
+
+#selector-root {
+  height: 100%;
+}
+.avatar-selector {
+  overflow: hidden;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+
+  &__previous-button:focus, &__next-button:focus {
+    outline: none;
+  }
+  &__previous-button:active, &__next-button:active {
+    color: grey;
+  }
+  &__previous-button, &__next-button {
+    position: absolute;
+    top: 50%;
+    margin-top: -0.5em;
+    appearance: none;
+    -moz-appearance: none;
+    -webkit-appearance: none;
+    background: transparent;
+    color: white;
+    border: none;
+    font-size: 64pt;
+  }
+  &__previous-button {
+    left: 0.2em;
+  }
+  &__next-button {
+    right: 0.2em;
+  }
+  &__loading {
+    @extend %default-font;
+    display: block;
+    position: absolute;
+    top: 50%;
+    text-align: center;
+    color: white;
+  }
+}
diff --git a/src/assets/stylesheets/profile.scss b/src/assets/stylesheets/profile.scss
index 05b2e5d801f42cee41a9e245eb57daed200798cc..9b45afd389dd5006d1596ee11d94f41a1ee59650 100644
--- a/src/assets/stylesheets/profile.scss
+++ b/src/assets/stylesheets/profile.scss
@@ -8,17 +8,27 @@
   align-items: center;
   display: flex;
   pointer-events: auto;
+
+  &__avatar-selector {
+    border: none;
+    width: 95%;
+    height: 100%;
+    margin: 1em 0;
+  }
 }
 
 .profile-entry__box {
-  height: 150px;
   border-radius: 8px;
   display: flex;
   flex-direction: column;
   justify-content: space-between;
   align-items: center;
-  padding: 25px;
+  padding: 15px;
   flex: 1 1 100%;
+  width: 60vw;
+  min-width: 300px;
+  max-width: 700px;
+  height: 500px
 }
 
 .profile-entry__box--darkened {
@@ -42,6 +52,7 @@
   line-height: 2.0em;
   padding-left: 1.25em;
   padding-right: 1.25em;
+  margin: 0.5em 0;
 }
 
 .profile-entry__form-submit {
diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json
index 04b25e90c782c04b60860524f98be0600627fcfe..4c9af592fa7b15592af0961f4df84aaf3b1fe2d5 100644
--- a/src/assets/translations.data.json
+++ b/src/assets/translations.data.json
@@ -16,6 +16,7 @@
     "profile.save": "SAVE",
     "profile.display_name.validation_warning": "Alphanumerics and hyphens. At least 3 characters, no more than 32",
     "profile.header": "Your identity",
+    "profile.avatar-selector.loading": "Loading Avatars...",
     "audio.title": "Test your audio",
     "audio.subtitle-desktop": "Confirm HMD speaker output",
     "audio.subtitle-mobile": "Earphones are recommended",
diff --git a/src/avatar-selector.html b/src/avatar-selector.html
new file mode 100644
index 0000000000000000000000000000000000000000..8496ef94d7f95ec90d29ae5c7e3c030c15659238
--- /dev/null
+++ b/src/avatar-selector.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
+  <% if(NODE_ENV === "production") { %>
+    <script src="https://cdn.rawgit.com/brianpeiris/aframe/845825ae694449524c185c44a314d361eead4680/dist/aframe-master.min.js"></script>
+  <% } else { %>
+    <script src="https://cdn.rawgit.com/brianpeiris/aframe/845825ae694449524c185c44a314d361eead4680/dist/aframe-master.js"></script>
+  <% } %>
+</head>
+
+<body>
+  <div id="selector-root"></div>
+</body>
+
+</html>
diff --git a/src/avatar-selector.js b/src/avatar-selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..34731ff37823ba04f3899dd02d34bdc6a8453e4c
--- /dev/null
+++ b/src/avatar-selector.js
@@ -0,0 +1,53 @@
+import ReactDOM from "react-dom";
+import React from "react";
+import queryString from "query-string";
+import { IntlProvider, FormattedMessage, addLocaleData } from "react-intl";
+import en from "react-intl/locale-data/en";
+
+import "./assets/stylesheets/avatar-selector.scss";
+import "./vendor/GLTFLoader";
+
+import "./components/animation-mixer";
+import "./components/audio-feedback";
+import "./components/loop-animation";
+import "./elements/a-progressive-asset";
+import "./gltf-component-mappings";
+import { avatars } from "./assets/avatars/avatars.json";
+import { avatarIds } from "./utils/identity";
+
+import { App } from "./App";
+import AvatarSelector from "./react-components/avatar-selector";
+import localeData from "./assets/translations.data.json";
+
+window.APP = new App();
+const hash = queryString.parse(location.hash);
+const isMobile = AFRAME.utils.device.isMobile();
+if (hash.quality) {
+  window.APP.quality = hash.quality;
+} else {
+  window.APP.quality = isMobile ? "low" : "high";
+}
+
+const lang = ((navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage)
+  .toLowerCase()
+  .split(/[_-]+/)[0];
+addLocaleData([...en]);
+const messages = localeData[lang] || localeData.en;
+
+function postAvatarIdToParent(newAvatarId) {
+  window.parent.postMessage({ avatarId: newAvatarId }, location.origin);
+}
+
+function mountUI() {
+  const hash = queryString.parse(location.hash);
+  const avatarId = hash.avatar_id;
+  ReactDOM.render(
+    <IntlProvider locale={lang} messages={messages}>
+      <AvatarSelector {...{ avatars, avatarId, onChange: postAvatarIdToParent }} />
+    </IntlProvider>,
+    document.getElementById("selector-root")
+  );
+}
+
+window.addEventListener("hashchange", mountUI);
+document.addEventListener("DOMContentLoaded", mountUI);
diff --git a/src/components/player-info.js b/src/components/player-info.js
index 8cb265a67b9c67ac26510d6b8a03efb59703c931..ac2fc6b55fcdaf0215e26327368c07dd67e88fcc 100644
--- a/src/components/player-info.js
+++ b/src/components/player-info.js
@@ -1,7 +1,7 @@
 AFRAME.registerComponent("player-info", {
   schema: {
     displayName: { type: "string" },
-    avatar: { type: "string" }
+    avatarSrc: { type: "string" }
   },
   init() {
     this.applyProperties = this.applyProperties.bind(this);
@@ -24,8 +24,8 @@ AFRAME.registerComponent("player-info", {
     }
 
     const modelEl = this.el.querySelector(".model");
-    if (this.data.avatar && modelEl) {
-      modelEl.setAttribute("src", this.data.avatar);
+    if (this.data.avatarSrc && modelEl) {
+      modelEl.setAttribute("src", this.data.avatarSrc);
     }
   }
 });
diff --git a/src/elements/a-gltf-entity.js b/src/elements/a-gltf-entity.js
index e88f7a76bf6ee4218f37997d9497e576b3bed55a..33489e2bc1792aa2b9dd4bddca360caaa39c509b 100644
--- a/src/elements/a-gltf-entity.js
+++ b/src/elements/a-gltf-entity.js
@@ -2,6 +2,9 @@ const GLTFCache = {};
 
 AFRAME.AGLTFEntity = {
   defaultInflator(el, componentName, componentData) {
+    if (!AFRAME.components[componentName]) {
+      throw new Error(`Inflator failed. "${componentName}" component does not exist.`);
+    }
     if (AFRAME.components[componentName].multiple && Array.isArray(componentData)) {
       for (let i = 0; i < componentData.length; i++) {
         el.setAttribute(componentName + "__" + i, componentData[i]);
@@ -220,7 +223,10 @@ AFRAME.registerElement("a-gltf-entity", {
         this.querySelectorAll(":scope > template").forEach(templateEl =>
           this.templates.push({
             selector: templateEl.getAttribute("data-selector"),
-            templateRoot: document.importNode(templateEl.content.firstElementChild, true)
+            templateRoot: document.importNode(
+              templateEl.firstElementChild || templateEl.content.firstElementChild,
+              true
+            )
           })
         );
       }
@@ -232,6 +238,10 @@ AFRAME.registerElement("a-gltf-entity", {
           // If the src attribute is a selector, get the url from the asset item.
           if (src && src.charAt(0) === "#") {
             const assetEl = document.getElementById(src.substring(1));
+            if (!assetEl) { 
+              console.warn(`Attempted to use non-existent asset ${src} as src for`, this);
+              return;
+            }
 
             const fallbackSrc = assetEl.getAttribute("src");
             const highSrc = assetEl.getAttribute("high-src");
@@ -282,8 +292,7 @@ AFRAME.registerElement("a-gltf-entity", {
 
           this.emit("model-loaded", { format: "gltf", model: this.model });
         } catch (e) {
-          const message = (e && e.message) || "Failed to load glTF model";
-          console.error(message);
+          console.error("Failed to load glTF model", e.message, this);
           this.emit("model-error", { format: "gltf", src });
         }
       }
@@ -303,7 +312,6 @@ AFRAME.registerElement("a-gltf-entity", {
         if (attr === "src") {
           this.applySrc(newVal);
         }
-        AFRAME.AEntity.prototype.attributeChangedCallback.call(this, attr, oldVal, newVal);
       }
     },
 
diff --git a/src/elements/a-progressive-asset.js b/src/elements/a-progressive-asset.js
index a367e374aece30163bc73802ab8cf11950f28c1a..8bf922aa17b10ce6eec09baf413e93dcf6d871f1 100644
--- a/src/elements/a-progressive-asset.js
+++ b/src/elements/a-progressive-asset.js
@@ -9,17 +9,17 @@ AFRAME.registerElement("a-progressive-asset", {
       value() {
         this.data = null;
         this.isAssetItem = true;
+      }
+    },
 
+    attachedCallback: {
+      value() {
         if (!this.parentNode.fileLoader) {
           throw new Error("a-progressive-asset must be the child of an a-assets element.");
         }
 
         this.fileLoader = this.parentNode.fileLoader;
-      }
-    },
 
-    attachedCallback: {
-      value() {
         const self = this;
         const fallbackSrc = this.getAttribute("src");
         const highSrc = this.getAttribute("high-src");
diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..c534c8ac2ea7997d96ea510649e0735a4f734e57
--- /dev/null
+++ b/src/react-components/avatar-selector.js
@@ -0,0 +1,126 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { injectIntl, FormattedMessage } from 'react-intl';
+import FontAwesomeIcon from '@fortawesome/react-fontawesome';
+import faAngleLeft from '@fortawesome/fontawesome-free-solid/faAngleLeft';
+import faAngleRight from '@fortawesome/fontawesome-free-solid/faAngleRight';
+
+class AvatarSelector extends Component {
+  static propTypes = {
+    avatars: PropTypes.array,
+    avatarId: PropTypes.string,
+    onChange: PropTypes.func,
+  }
+
+  getAvatarIndex = (direction=0) => {
+    const currAvatarIndex = this.props.avatars.findIndex(avatar => avatar.id === this.props.avatarId);
+    const numAvatars = this.props.avatars.length;
+    return ((currAvatarIndex + direction) % numAvatars + numAvatars) % numAvatars;
+  }
+  nextAvatarIndex = () => this.getAvatarIndex(1)
+  previousAvatarIndex = () => this.getAvatarIndex(-1)
+
+  emitChangeToNext = () => {
+    const nextAvatarId = this.props.avatars[this.nextAvatarIndex()].id;
+    this.props.onChange(nextAvatarId);
+  }
+
+  emitChangeToPrevious = () => {
+    const previousAvatarId = this.props.avatars[this.previousAvatarIndex()].id;
+    this.props.onChange(previousAvatarId);
+  }
+
+  componentDidUpdate(prevProps) {
+    if (this.props.avatarId !== prevProps.avatarId) { 
+      // HACK - a-animation ought to restart the animation when the `to` attribute changes, but it doesn't
+      // so we need to force it here.
+      const currRot = this.animation.parentNode.getAttribute('rotation');
+      this.animation.setAttribute('from', `${currRot.x} ${currRot.y} ${currRot.z}`);
+      this.animation.stop();
+      this.animation.handleMixinUpdate();
+      this.animation.start();
+    }
+  }
+
+  render () {
+    const avatarAssets = this.props.avatars.map(avatar => (
+      <a-progressive-asset
+        id={avatar.id}
+        key={avatar.id}
+        response-type="arraybuffer"
+        high-src={`./src/assets/avatars/${avatar.models.high}`}
+        low-src={`./src/assets/avatars/${avatar.models.low}`}
+      ></a-progressive-asset>
+    ));
+
+    const avatarEntities = this.props.avatars.map((avatar, i) => (
+      <a-entity key={avatar.id} position="0 0 0" rotation={`0 ${360 * -i / this.props.avatars.length} 0`}>
+        <a-gltf-entity position="0 0 5" rotation="0 0 0" src={'#' + avatar.id} inflate="true">
+          <template data-selector=".RootScene">
+            <a-entity animation-mixer></a-entity>
+          </template>
+          <a-animation
+            attribute="rotation"
+            dur="2000"
+            to={`0 ${this.getAvatarIndex() === i ? 360 : 0} 0`}
+            repeat="indefinite">
+          </a-animation>
+        </a-gltf-entity>
+      </a-entity>
+    ));
+
+    return (
+      <div className="avatar-selector">
+      <span className="avatar-selector__loading">
+        <FormattedMessage id="profile.avatar-selector.loading"/>
+      </span>
+      <a-scene vr-mode-ui="enabled: false" ref={sce => this.scene = sce}>
+        <a-assets>
+          {avatarAssets}
+          <a-asset-item
+            id="meeting-space1-mesh"
+            response-type="arraybuffer"
+            src="./src/assets/environments/MeetingSpace1_mesh.glb"
+          ></a-asset-item>
+        </a-assets>
+
+        <a-entity data-avatar={this.props.avatarId}>
+          <a-animation
+            ref={anm => this.animation = anm}
+            attribute="rotation"
+            dur="1000"
+            easing="ease-out"
+            to={`0 ${360 * this.getAvatarIndex() / this.props.avatars.length + 180} 0`}>
+          </a-animation>
+        {avatarEntities}
+        </a-entity>
+
+        <a-entity position="0 1.5 -5.6" rotation="-10 180 0" camera></a-entity>
+
+        <a-entity
+          hide-when-quality="low"
+          light="type: directional; color: #F9FFCE; intensity: 0.6"
+          position="0 5 -15"
+        ></a-entity>
+        <a-entity
+          hide-when-quality="low"
+          light="type: ambient; color: #FFF"
+        ></a-entity>
+        <a-gltf-entity
+          id="meeting-space"
+          src="#meeting-space1-mesh"
+          position="0 0 0"
+        ></a-gltf-entity>
+      </a-scene>
+      <button className="avatar-selector__previous-button" onClick={this.emitChangeToPrevious}>
+        <FontAwesomeIcon icon={faAngleLeft} />
+      </button>
+      <button className="avatar-selector__next-button" onClick={this.emitChangeToNext}>
+        <FontAwesomeIcon icon={faAngleRight} />
+      </button>
+      </div>
+    );
+  }
+}
+
+export default injectIntl(AvatarSelector);
diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js
index 28c8cd7d9ce8c8bf9902f6a654c835bbd7fe95ea..cc20df8a4679475744f7c40f997f87b71d2b4daf 100644
--- a/src/react-components/profile-entry-panel.js
+++ b/src/react-components/profile-entry-panel.js
@@ -13,29 +13,50 @@ class ProfileEntryPanel extends Component {
   constructor(props) {
     super(props);
     window.store = this.props.store;
-    this.state = {name: this.props.store.state.profile.display_name};
+    this.state = {
+      display_name: this.props.store.state.profile.display_name,
+      avatar_id: this.props.store.state.profile.avatar_id,
+    };
     this.props.store.addEventListener("statechanged", this.storeUpdated);
   }
 
   storeUpdated = () => {
-    this.setState({name: this.props.store.state.profile.display_name});
+    const { avatar_id, display_name } = this.props.store.state.profile;
+    this.setState({ avatar_id, display_name });
   }
 
-  saveName = (e) => {
+  saveStateAndFinish = (e) => {
     e.preventDefault();
-    this.props.store.update({ profile: { display_name: this.nameInput.value } });
+    this.props.store.update({profile: {
+      display_name: this.state.display_name,
+      avatar_id: this.state.avatar_id
+    }});
     this.props.finished();
   }
 
+  stopPropagation = (e) => {
+    e.stopPropagation();
+  }
+
+  setAvatarStateFromIframeMessage = (e) => {
+    if (e.source !== this.avatarSelector.contentWindow) { return; }
+    this.setState({avatar_id: e.data.avatarId});
+  }
+
   componentDidMount() {
     // stop propagation so that avatar doesn't move when wasd'ing during text input.
-    this.nameInput.addEventListener('keydown', e => e.stopPropagation());
-    this.nameInput.addEventListener('keypress', e => e.stopPropagation());
-    this.nameInput.addEventListener('keyup', e => e.stopPropagation());
+    this.nameInput.addEventListener('keydown', this.stopPropagation);
+    this.nameInput.addEventListener('keypress', this.stopPropagation);
+    this.nameInput.addEventListener('keyup', this.stopPropagation);
+    window.addEventListener('message', this.setAvatarStateFromIframeMessage);
   }
-  
+
   componentWillUnmount() {
     this.props.store.removeEventListener('statechanged', this.storeUpdated);
+    this.nameInput.removeEventListener('keydown', this.stopPropagation);
+    this.nameInput.removeEventListener('keypress', this.stopPropagation);
+    this.nameInput.removeEventListener('keyup', this.stopPropagation);
+    window.removeEventListener('message', this.setAvatarStateFromIframeMessage);
   }
 
   render () {
@@ -43,19 +64,23 @@ class ProfileEntryPanel extends Component {
 
     return (
       <div className="profile-entry">
-        <form onSubmit={this.saveName}>
+        <form onSubmit={this.saveStateAndFinish}>
         <div className="profile-entry__box profile-entry__box--darkened">
           <div className="profile-entry__subtitle">
             <FormattedMessage id="profile.header"/>
           </div>
           <input
             className="profile-entry__form-field-text"
-            value={this.state.name} onChange={(e) => this.setState({name: e.target.value})}
+            value={this.state.display_name} onChange={(e) => this.setState({display_name: e.target.value})}
             required pattern={SCHEMA.definitions.profile.properties.display_name.pattern}
             title={formatMessage({ id: "profile.display_name.validation_warning" })}
             ref={inp => this.nameInput = inp}/>
+          <iframe
+            className="profile-entry__avatar-selector"
+            src={`avatar-selector.html#avatar_id=${this.state.avatar_id}`}
+            ref={ifr => this.avatarSelector = ifr}></iframe>
           <input className="profile-entry__form-submit" type="submit" value={formatMessage({ id: "profile.save" }) }/>
-          </div>
+        </div>
         </form>
       </div>
     );
diff --git a/src/room.html b/src/room.html
index 132e6af23c42d61553fc1c78c43ff0afc0cf188b..5f63358aa5e17651ee03d9978f098035e533b164 100644
--- a/src/room.html
+++ b/src/room.html
@@ -29,13 +29,69 @@
             <img id="avatar"  src="./assets/hud/avatar.jpg" >
 
             <a-progressive-asset
-                id="bot-skinned-mesh"
+                id="botdefault"
                 response-type="arraybuffer"
                 src="./assets/avatars/BotDefault_Avatar_Unlit.glb"
                 high-src="./assets/avatars/BotDefault_Avatar.glb"
                 low-src="./assets/avatars/BotDefault_Avatar_Unlit.glb"
             ></a-progressive-asset>
-            <a-asset-item id="bot-dom-mesh" response-type="arraybuffer" src="./assets/avatars/BotDom_Avatar.glb"></a-asset-item>
+            <a-progressive-asset
+                id="botbobo"
+                response-type="arraybuffer"
+                src="./assets/avatars/BotBobo_Avatar_Unlit.glb"
+                high-src="./assets/avatars/BotBobo_Avatar.glb"
+                low-src="./assets/avatars/BotBobo_Avatar_Unlit.glb"
+            ></a-progressive-asset>
+            <a-progressive-asset
+                id="botdom"
+                response-type="arraybuffer"
+                src="./assets/avatars/BotDom_Avatar_Unlit.glb"
+                high-src="./assets/avatars/BotDom_Avatar.glb"
+                low-src="./assets/avatars/BotDom_Avatar_Unlit.glb"
+            ></a-progressive-asset>
+            <a-progressive-asset
+                id="botgreg"
+                response-type="arraybuffer"
+                src="./assets/avatars/BotGreg_Avatar_Unlit.glb"
+                high-src="./assets/avatars/BotGreg_Avatar.glb"
+                low-src="./assets/avatars/BotGreg_Avatar_Unlit.glb"
+            ></a-progressive-asset>
+            <a-progressive-asset
+                id="botguest"
+                response-type="arraybuffer"
+                src="./assets/avatars/BotGuest_Avatar_Unlit.glb"
+                high-src="./assets/avatars/BotGuest_Avatar.glb"
+                low-src="./assets/avatars/BotGuest_Avatar_Unlit.glb"
+            ></a-progressive-asset>
+            <a-progressive-asset
+                id="botjim"
+                response-type="arraybuffer"
+                src="./assets/avatars/BotJim_Avatar_Unlit.glb"
+                high-src="./assets/avatars/BotJim_Avatar.glb"
+                low-src="./assets/avatars/BotJim_Avatar_Unlit.glb"
+            ></a-progressive-asset>
+            <a-progressive-asset
+                id="botpinky"
+                response-type="arraybuffer"
+                src="./assets/avatars/BotRobert_Avatar_Unlit.glb"
+                high-src="./assets/avatars/BotRobert_Avatar.glb"
+                low-src="./assets/avatars/BotRobert_Avatar_Unlit.glb"
+            ></a-progressive-asset>
+            <a-progressive-asset
+                id="botrobert"
+                response-type="arraybuffer"
+                src="./assets/avatars/BotRobert_Avatar_Unlit.glb"
+                high-src="./assets/avatars/BotRobert_Avatar.glb"
+                low-src="./assets/avatars/BotRobert_Avatar_Unlit.glb"
+            ></a-progressive-asset>
+            <a-progressive-asset
+                id="botwoody"
+                response-type="arraybuffer"
+                src="./assets/avatars/BotWoody_Avatar_Unlit.glb"
+                high-src="./assets/avatars/BotWoody_Avatar.glb"
+                low-src="./assets/avatars/BotWoody_Avatar_Unlit.glb"
+            ></a-progressive-asset>
+
             <a-asset-item id="watch-model" response-type="arraybuffer" src="./assets/hud/watch.glb"></a-asset-item>
 
             <a-asset-item id="meeting-space1-mesh" response-type="arraybuffer" src="./assets/environments/MeetingSpace1_mesh.glb"></a-asset-item>
@@ -124,7 +180,7 @@
         <!-- Interactables -->
         <a-entity id="counter" networked-counter="max: 3; ttl: 120"></a-entity>
 
-        <a-entity 
+        <a-entity
             gltf-model="#interactable-duck"
             scale="2 2 2"
             class="interactable" 
@@ -215,7 +271,7 @@
             <a-gltf-entity class="model" inflate="true">
                 <template data-selector=".RootScene">
                     <a-entity
-                        ik-controller 
+                        ik-controller
                         animated-robot-hands
                         animation-mixer
                     ></a-entity>
@@ -318,12 +374,12 @@
             xr="ar: false"
         ></a-entity>
 
-        <a-cylinder 
-            position="0 0.45 0" 
-            material="visible: false" 
-            height="1" radius="3.1" 
-            segments-radial="12" 
-            static-body 
+        <a-cylinder
+            position="0 0.45 0"
+            material="visible: false"
+            height="1" radius="3.1"
+            segments-radial="12"
+            static-body
             class="collidable"
         ></a-cylinder>
 
diff --git a/src/room.js b/src/room.js
index 909e8977f7bd4ce6d30e6ce4ddfc183a7e685c3b..232bc18accb2d4150f3213e09f1cbaac5d0c4d47 100644
--- a/src/room.js
+++ b/src/room.js
@@ -113,13 +113,11 @@ async function exitScene() {
   document.body.removeChild(scene);
 }
 
-function updatePlayerInfoFromStore() {
+function applyProfileFromStore(playerRig) {
   const displayName = store.state.profile.display_name;
-  const qs = queryString.parse(location.search);
-  const playerRig = document.querySelector("#player-rig");
   playerRig.setAttribute("player-info", {
     displayName,
-    avatar: qs.avatar || "#bot-skinned-mesh"
+    avatarSrc: '#' + (store.state.profile.avatar_id || "botdefault")
   });
   document.querySelector("a-scene").emit("username-changed", { username: displayName });
 }
@@ -127,8 +125,6 @@ function updatePlayerInfoFromStore() {
 async function enterScene(mediaStream, enterInVR) {
   const scene = document.querySelector("a-scene");
   const playerRig = document.querySelector("#player-rig");
-  const qs = queryString.parse(location.search);
-
   document.querySelector("a-scene canvas").classList.remove("blurred");
   registerNetworkSchemas();
 
@@ -155,8 +151,9 @@ async function enterScene(mediaStream, enterInVR) {
     playerRig.setAttribute("virtual-gamepad-controls", {});
   }
 
-  updatePlayerInfoFromStore();
-  store.addEventListener("statechanged", updatePlayerInfoFromStore);
+  const applyProfileOnPlayerRig = applyProfileFromStore.bind(null, playerRig);
+  applyProfileOnPlayerRig();
+  store.addEventListener("statechanged", applyProfileOnPlayerRig);
 
   const avatarScale = parseInt(qs.avatar_scale, 10);
 
diff --git a/src/storage/store.js b/src/storage/store.js
index 5b00b7ac8f5570c28651587997ae4c0546a3b722..037e27bf1d9603a210423baa5ee6543c2ee77fdb 100644
--- a/src/storage/store.js
+++ b/src/storage/store.js
@@ -17,6 +17,7 @@ export const SCHEMA = {
       additionalProperties: false,
       properties: {
         display_name: { type: "string", pattern: "^[A-Za-z0-9-]{3,32}$" },
+        avatar_id: { type: "string" },
       }
     }
   },
diff --git a/src/utils/identity.js b/src/utils/identity.js
index 72cabdf008bc2574b4408d62c8f7f17f30fc9a59..727be75580bae2701de1c8ae2c81315d60a635b5 100644
--- a/src/utils/identity.js
+++ b/src/utils/identity.js
@@ -1,3 +1,5 @@
+import { avatars } from "../assets/avatars/avatars.json";
+
 const names = [
   "albattani",
   "allen",
@@ -161,7 +163,16 @@ const names = [
   "yonath"
 ];
 
+function selectRandom(arr) {
+   return arr[Math.floor(Math.random() * arr.length)]
+}
+
+export const avatarIds = avatars.map(av => av.id);
+
 export function generateDefaultProfile() {
-  const name = names[Math.floor(Math.random() * names.length)];
-  return { display_name: name.replace(/^./, name[0].toUpperCase()) };
+  const name = selectRandom(names);
+  return {
+    display_name: name.replace(/^./, name[0].toUpperCase()) ,
+    avatar_id: selectRandom(avatarIds)
+  };
 }
diff --git a/webpack.config.js b/webpack.config.js
index 4996b8925731f3e5912b954dafa5c6b1dc2a66c2..d404babbb6d5af438271621b4e3f2fbac235a266 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -77,6 +77,7 @@ const config = {
   entry: {
     lobby: path.join(__dirname, "src", "lobby.js"),
     room: path.join(__dirname, "src", "room.js"),
+    'avatar-selector': path.join(__dirname, "src", "avatar-selector.js"),
     onboarding: path.join(__dirname, "src", "onboarding.js")
   },
   output: {
@@ -196,6 +197,12 @@ const config = {
       chunks: ["room"],
       inject: "head"
     }),
+    new HTMLWebpackPlugin({
+      filename: "avatar-selector.html",
+      template: path.join(__dirname, "src", "avatar-selector.html"),
+      chunks: ["avatar-selector"],
+      inject: "head"
+    }),
     new HTMLWebpackPlugin({
       filename: "onboarding.html",
       template: path.join(__dirname, "src", "onboarding.html"),
diff --git a/yarn.lock b/yarn.lock
index b2fc3a25d7605205ddad6244206d2076f41149ac..71b37b2f51e39ff013774a7a96b8e2e23c8441b2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -71,6 +71,28 @@
     lodash "^4.2.0"
     to-fast-properties "^2.0.0"
 
+"@fortawesome/fontawesome-common-types@^0.1.3":
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.1.3.tgz#8475e0f2d1ad1f858c4ec2e76ed9a2456a09ad83"
+
+"@fortawesome/fontawesome-free-solid@^5.0.9":
+  version "5.0.9"
+  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free-solid/-/fontawesome-free-solid-5.0.9.tgz#456155a1cd82a0342ffe6a869d5a54fdadd78548"
+  dependencies:
+    "@fortawesome/fontawesome-common-types" "^0.1.3"
+
+"@fortawesome/fontawesome@^1.1.5":
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome/-/fontawesome-1.1.5.tgz#c7cfafdd3364245626293cc670357f9fb8487170"
+  dependencies:
+    "@fortawesome/fontawesome-common-types" "^0.1.3"
+
+"@fortawesome/react-fontawesome@^0.0.18":
+  version "0.0.18"
+  resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.0.18.tgz#4e0eb1cf9797715a67bb7705ae084fa0a410f185"
+  dependencies:
+    humps "^2.0.1"
+
 JSONStream@^1.0.3:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea"
@@ -3911,6 +3933,10 @@ https-browserify@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
 
+humps@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa"
+
 iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.13:
   version "0.4.19"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"