diff --git a/res/css/views/elements/_MessageEditor.scss b/res/css/views/elements/_MessageEditor.scss
index cc5649a224..7cf96afb11 100644
--- a/res/css/views/elements/_MessageEditor.scss
+++ b/res/css/views/elements/_MessageEditor.scss
@@ -21,6 +21,7 @@ limitations under the License.
     // padding around and in the editor.
     // Actual values from fiddling around in inspector
     margin: -7px -10px -5px -10px;
+    overflow: visible !important;   // override mx_EventTile_content
 
     .mx_MessageEditor_editor {
         border-radius: 4px;
@@ -33,20 +34,28 @@ limitations under the License.
         max-height: 200px;
         overflow-x: auto;
 
-        span {
-            display: inline-block;
-            padding: 0 5px;
-            border-radius: 4px;
-            color: white;
-        }
+        span.mx_UserPill, span.mx_RoomPill {
+            padding-left: 21px;
+            position: relative;
 
-        span.user-pill, span.room-pill {
-            border-radius: 16px;
-            display: inline-block;
-            color: $primary-fg-color;
-            background-color: $other-user-pill-bg-color;
-            padding-left: 5px;
-            padding-right: 5px;
+            // avatar psuedo element
+            &::before {
+                position: absolute;
+                left: 2px;
+                top: 2px;
+                content: var(--avatar-letter);
+                width: 16px;
+                height: 16px;
+                background: var(--avatar-background), $avatar-bg-color;
+                color: $avatar-initial-color;
+                background-repeat: no-repeat;
+                background-size: 16px;
+                border-radius: 8px;
+                text-align: center;
+                font-weight: normal;
+                line-height: 16px;
+                font-size: 10.4px;
+            }
         }
     }
 
@@ -61,7 +70,7 @@ limitations under the License.
         z-index: 100;
         right: 0;
         margin: 0 -110px 0 0;
-        padding-right: 104px;
+        padding-right: 147px;
 
         .mx_AccessibleButton {
             margin-left: 5px;
diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index e67d9d4107..72881231f8 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -40,7 +40,12 @@ limitations under the License.
 }
 
 .mx_EventTile_continuation {
-    padding-top: 0px ! important;
+    padding-top: 0px !important;
+
+    &.mx_EventTile_isEditing {
+        padding-top: 5px !important;
+        margin-top: -5px;
+    }
 }
 
 .mx_EventTile_isEditing {
diff --git a/src/Avatar.js b/src/Avatar.js
index 99b558fa93..2e15874b4e 100644
--- a/src/Avatar.js
+++ b/src/Avatar.js
@@ -17,6 +17,7 @@ limitations under the License.
 'use strict';
 import {ContentRepo} from 'matrix-js-sdk';
 import MatrixClientPeg from './MatrixClientPeg';
+import DMRoomMap from './utils/DMRoomMap';
 
 module.exports = {
     avatarUrlForMember: function(member, width, height, resizeMethod) {
@@ -58,4 +59,71 @@ module.exports = {
         }
         return require('../res/img/' + images[total % images.length] + '.png');
     },
+
+    /**
+     * returns the first (non-sigil) character of 'name',
+     * converted to uppercase
+     * @param {string} name
+     * @return {string} the first letter
+     */
+    getInitialLetter(name) {
+        if (name.length < 1) {
+            return undefined;
+        }
+
+        let idx = 0;
+        const initial = name[0];
+        if ((initial === '@' || initial === '#' || initial === '+') && name[1]) {
+            idx++;
+        }
+
+        // string.codePointAt(0) would do this, but that isn't supported by
+        // some browsers (notably PhantomJS).
+        let chars = 1;
+        const first = name.charCodeAt(idx);
+
+        // check if it’s the start of a surrogate pair
+        if (first >= 0xD800 && first <= 0xDBFF && name[idx+1]) {
+            const second = name.charCodeAt(idx+1);
+            if (second >= 0xDC00 && second <= 0xDFFF) {
+                chars++;
+            }
+        }
+
+        const firstChar = name.substring(idx, idx+chars);
+        return firstChar.toUpperCase();
+    },
+
+    avatarUrlForRoom(room, width, height, resizeMethod) {
+        const explicitRoomAvatar = room.getAvatarUrl(
+            MatrixClientPeg.get().getHomeserverUrl(),
+            width,
+            height,
+            resizeMethod,
+            false,
+        );
+        if (explicitRoomAvatar) {
+            return explicitRoomAvatar;
+        }
+
+        let otherMember = null;
+        const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
+        if (otherUserId) {
+            otherMember = room.getMember(otherUserId);
+        } else {
+            // if the room is not marked as a 1:1, but only has max 2 members
+            // then still try to show any avatar (pref. other member)
+            otherMember = room.getAvatarFallbackMember();
+        }
+        if (otherMember) {
+            return otherMember.getAvatarUrl(
+                MatrixClientPeg.get().getHomeserverUrl(),
+                width,
+                height,
+                resizeMethod,
+                false,
+            );
+        }
+        return null;
+    },
 };
diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js
index 5a6c5ca581..db663e08a2 100644
--- a/src/components/views/avatars/BaseAvatar.js
+++ b/src/components/views/avatars/BaseAvatar.js
@@ -133,38 +133,6 @@ module.exports = React.createClass({
         }
     },
 
-    /**
-     * returns the first (non-sigil) character of 'name',
-     * converted to uppercase
-     */
-    _getInitialLetter: function(name) {
-        if (name.length < 1) {
-            return undefined;
-        }
-
-        let idx = 0;
-        const initial = name[0];
-        if ((initial === '@' || initial === '#' || initial === '+') && name[1]) {
-            idx++;
-        }
-
-        // string.codePointAt(0) would do this, but that isn't supported by
-        // some browsers (notably PhantomJS).
-        let chars = 1;
-        const first = name.charCodeAt(idx);
-
-        // check if it’s the start of a surrogate pair
-        if (first >= 0xD800 && first <= 0xDBFF && name[idx+1]) {
-            const second = name.charCodeAt(idx+1);
-            if (second >= 0xDC00 && second <= 0xDFFF) {
-                chars++;
-            }
-        }
-
-        const firstChar = name.substring(idx, idx+chars);
-        return firstChar.toUpperCase();
-    },
-
     render: function() {
         const imageUrl = this.state.imageUrls[this.state.urlsIndex];
 
@@ -175,7 +143,7 @@ module.exports = React.createClass({
         } = this.props;
 
         if (imageUrl === this.state.defaultImageUrl) {
-            const initialLetter = this._getInitialLetter(name);
+            const initialLetter = AvatarLogic.getInitialLetter(name);
             const textNode = (
                 <span className="mx_BaseAvatar_initial" aria-hidden="true"
                     style={{ fontSize: (width * 0.65) + "px",
diff --git a/src/components/views/avatars/RoomAvatar.js b/src/components/views/avatars/RoomAvatar.js
index 38f088238f..557a4d8dbf 100644
--- a/src/components/views/avatars/RoomAvatar.js
+++ b/src/components/views/avatars/RoomAvatar.js
@@ -19,7 +19,7 @@ import {ContentRepo} from "matrix-js-sdk";
 import MatrixClientPeg from "../../../MatrixClientPeg";
 import Modal from '../../../Modal';
 import sdk from "../../../index";
-import DMRoomMap from '../../../utils/DMRoomMap';
+import Avatar from '../../../Avatar';
 
 module.exports = React.createClass({
     displayName: 'RoomAvatar',
@@ -89,7 +89,6 @@ module.exports = React.createClass({
                 props.resizeMethod,
             ), // highest priority
             this.getRoomAvatarUrl(props),
-            this.getOneToOneAvatar(props), // lowest priority
         ].filter(function(url) {
             return (url != null && url != "");
         });
@@ -98,41 +97,14 @@ module.exports = React.createClass({
     getRoomAvatarUrl: function(props) {
         if (!props.room) return null;
 
-        return props.room.getAvatarUrl(
-            MatrixClientPeg.get().getHomeserverUrl(),
+        return Avatar.avatarUrlForRoom(
+            props.room,
             Math.floor(props.width * window.devicePixelRatio),
             Math.floor(props.height * window.devicePixelRatio),
             props.resizeMethod,
-            false,
         );
     },
 
-    getOneToOneAvatar: function(props) {
-        const room = props.room;
-        if (!room) {
-            return null;
-        }
-        let otherMember = null;
-        const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
-        if (otherUserId) {
-            otherMember = room.getMember(otherUserId);
-        } else {
-            // if the room is not marked as a 1:1, but only has max 2 members
-            // then still try to show any avatar (pref. other member)
-            otherMember = room.getAvatarFallbackMember();
-        }
-        if (otherMember) {
-            return otherMember.getAvatarUrl(
-                MatrixClientPeg.get().getHomeserverUrl(),
-                Math.floor(props.width * window.devicePixelRatio),
-                Math.floor(props.height * window.devicePixelRatio),
-                props.resizeMethod,
-                false,
-            );
-        }
-        return null;
-    },
-
     onRoomAvatarClick: function() {
         const avatarUrl = this.props.room.getAvatarUrl(
             MatrixClientPeg.get().getHomeserverUrl(),
diff --git a/src/components/views/elements/MessageEditor.js b/src/components/views/elements/MessageEditor.js
index 0c249d067b..1f3440d740 100644
--- a/src/components/views/elements/MessageEditor.js
+++ b/src/components/views/elements/MessageEditor.js
@@ -27,6 +27,7 @@ import Autocomplete from '../rooms/Autocomplete';
 import {PartCreator} from '../../../editor/parts';
 import {renderModel} from '../../../editor/render';
 import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
+import classNames from 'classnames';
 
 export default class MessageEditor extends React.Component {
     static propTypes = {
@@ -40,16 +41,17 @@ export default class MessageEditor extends React.Component {
 
     constructor(props, context) {
         super(props, context);
+        const room = this.context.matrixClient.getRoom(this.props.event.getRoomId());
         const partCreator = new PartCreator(
             () => this._autocompleteRef,
             query => this.setState({query}),
+            room,
         );
         this.model = new EditorModel(
-            parseEvent(this.props.event),
+            parseEvent(this.props.event, room),
             partCreator,
             this._updateEditorState,
         );
-        const room = this.context.matrixClient.getRoom(this.props.event.getRoomId());
         this.state = {
             autoComplete: null,
             room,
@@ -176,7 +178,7 @@ export default class MessageEditor extends React.Component {
             </div>;
         }
         const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
-        return <div className="mx_MessageEditor">
+        return <div className={classNames("mx_MessageEditor", this.props.className)}>
                 { autoComplete }
                 <div
                     className="mx_MessageEditor_editor"
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 5356572bb9..c4eb6f8c7d 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -471,7 +471,7 @@ module.exports = React.createClass({
     render: function() {
         if (this.props.isEditing) {
             const MessageEditor = sdk.getComponent('elements.MessageEditor');
-            return <MessageEditor event={this.props.mxEvent} />;
+            return <MessageEditor event={this.props.mxEvent} className="mx_EventTile_content" />;
         }
         const mxEvent = this.props.mxEvent;
         const content = mxEvent.getContent();
diff --git a/src/editor/autocomplete.js b/src/editor/autocomplete.js
index d2f73b1dff..731bb8d986 100644
--- a/src/editor/autocomplete.js
+++ b/src/editor/autocomplete.js
@@ -17,11 +17,12 @@ limitations under the License.
 import {UserPillPart, RoomPillPart, PlainPart} from "./parts";
 
 export default class AutocompleteWrapperModel {
-    constructor(updateCallback, getAutocompleterComponent, updateQuery) {
+    constructor(updateCallback, getAutocompleterComponent, updateQuery, room) {
         this._updateCallback = updateCallback;
         this._getAutocompleterComponent = getAutocompleterComponent;
         this._updateQuery = updateQuery;
         this._query = null;
+        this._room = room;
     }
 
     onEscape(e) {
@@ -83,7 +84,8 @@ export default class AutocompleteWrapperModel {
             case "@": {
                 const displayName = completion.completion;
                 const userId = completion.completionId;
-                return new UserPillPart(userId, displayName);
+                const member = this._room.getMember(userId);
+                return new UserPillPart(userId, displayName, member);
             }
             case "#": {
                 const displayAlias = completion.completionId;
diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js
index a7f28badb1..569e166ab0 100644
--- a/src/editor/deserialize.js
+++ b/src/editor/deserialize.js
@@ -17,7 +17,7 @@ limitations under the License.
 import { MATRIXTO_URL_PATTERN } from '../linkify-matrix';
 import { PlainPart, UserPillPart, RoomPillPart, NewlinePart } from "./parts";
 
-function parseHtmlMessage(html) {
+function parseHtmlMessage(html, room) {
     const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
     // no nodes from parsing here should be inserted in the document,
     // as scripts in event handlers, etc would be executed then.
@@ -37,8 +37,8 @@ function parseHtmlMessage(html) {
                         const resourceId = pillMatch[1]; // The room/user ID
                         const prefix = pillMatch[2]; // The first character of prefix
                         switch (prefix) {
-                            case "@": return new UserPillPart(resourceId, n.textContent);
-                            case "#": return new RoomPillPart(resourceId, n.textContent);
+                            case "@": return new UserPillPart(resourceId, n.textContent, room.getMember(resourceId));
+                            case "#": return new RoomPillPart(resourceId);
                             default: return new PlainPart(n.textContent);
                         }
                     }
@@ -54,10 +54,10 @@ function parseHtmlMessage(html) {
     return parts;
 }
 
-export function parseEvent(event) {
+export function parseEvent(event, room) {
     const content = event.getContent();
     if (content.format === "org.matrix.custom.html") {
-        return parseHtmlMessage(content.formatted_body || "");
+        return parseHtmlMessage(content.formatted_body || "", room);
     } else {
         const body = content.body || "";
         const lines = body.split("\n");
diff --git a/src/editor/parts.js b/src/editor/parts.js
index bf792b1ab9..1947be7d80 100644
--- a/src/editor/parts.js
+++ b/src/editor/parts.js
@@ -15,6 +15,8 @@ limitations under the License.
 */
 
 import AutocompleteWrapperModel from "./autocomplete";
+import Avatar from "../Avatar";
+import MatrixClientPeg from "../MatrixClientPeg";
 
 class BasePart {
     constructor(text = "") {
@@ -150,21 +152,21 @@ class PillPart extends BasePart {
 
     toDOMNode() {
         const container = document.createElement("span");
-        container.className = this.type;
+        container.className = this.className;
         container.appendChild(document.createTextNode(this.text));
+        this.setAvatar(container);
         return container;
     }
 
     updateDOMNode(node) {
         const textNode = node.childNodes[0];
         if (textNode.textContent !== this.text) {
-            // console.log("changing pill text from", textNode.textContent, "to", this.text);
             textNode.textContent = this.text;
         }
-        if (node.className !== this.type) {
-            // console.log("turning", node.className, "into", this.type);
-            node.className = this.type;
+        if (node.className !== this.className) {
+            node.className = this.className;
         }
+        this.setAvatar(node);
     }
 
     canUpdateDOMNode(node) {
@@ -174,6 +176,20 @@ class PillPart extends BasePart {
                node.childNodes[0].nodeType === Node.TEXT_NODE;
     }
 
+    // helper method for subclasses
+    _setAvatarVars(node, avatarUrl, initialLetter) {
+        const avatarBackground = `url('${avatarUrl}')`;
+        const avatarLetter = `'${initialLetter}'`;
+        // check if the value is changing,
+        // otherwise the avatars flicker on every keystroke while updating.
+        if (node.style.getPropertyValue("--avatar-background") !== avatarBackground) {
+            node.style.setProperty("--avatar-background", avatarBackground);
+        }
+        if (node.style.getPropertyValue("--avatar-letter") !== avatarLetter) {
+            node.style.setProperty("--avatar-letter", avatarLetter);
+        }
+    }
+
     get canEdit() {
         return false;
     }
@@ -218,17 +234,71 @@ export class NewlinePart extends BasePart {
 export class RoomPillPart extends PillPart {
     constructor(displayAlias) {
         super(displayAlias, displayAlias);
+        this._room = this._findRoomByAlias(displayAlias);
+    }
+
+    _findRoomByAlias(alias) {
+        const client = MatrixClientPeg.get();
+        if (alias[0] === '#') {
+            return client.getRooms().find((r) => {
+                return r.getAliases().includes(alias);
+            });
+        } else {
+            return client.getRoom(alias);
+        }
+    }
+
+    setAvatar(node) {
+        let initialLetter = "";
+        let avatarUrl = Avatar.avatarUrlForRoom(this._room, 16 * window.devicePixelRatio, 16 * window.devicePixelRatio);
+        if (!avatarUrl) {
+            initialLetter = Avatar.getInitialLetter(this._room.name);
+            avatarUrl = `../../${Avatar.defaultAvatarUrlForString(this._room.roomId)}`;
+        }
+        this._setAvatarVars(node, avatarUrl, initialLetter);
     }
 
     get type() {
         return "room-pill";
     }
+
+    get className() {
+        return "mx_RoomPill mx_Pill";
+    }
 }
 
 export class UserPillPart extends PillPart {
+    constructor(userId, displayName, member) {
+        super(userId, displayName);
+        this._member = member;
+    }
+
+    setAvatar(node) {
+        const name = this._member.name || this._member.userId;
+        const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(this._member.userId);
+        let avatarUrl = Avatar.avatarUrlForMember(
+            this._member,
+            16 * window.devicePixelRatio,
+            16 * window.devicePixelRatio);
+        let initialLetter = "";
+        if (avatarUrl === defaultAvatarUrl) {
+            // the url from defaultAvatarUrlForString is meant to go in an img element,
+            // which has the base of the document. we're using it in css,
+            // which has the base of the theme css file, two levels deeper than the document,
+            // so go up to the level of the document.
+            avatarUrl = `../../${avatarUrl}`;
+            initialLetter = Avatar.getInitialLetter(name);
+        }
+        this._setAvatarVars(node, avatarUrl, initialLetter);
+    }
+
     get type() {
         return "user-pill";
     }
+
+    get className() {
+        return "mx_UserPill mx_Pill";
+    }
 }
 
 
@@ -256,9 +326,14 @@ export class PillCandidatePart extends PlainPart {
 }
 
 export class PartCreator {
-    constructor(getAutocompleterComponent, updateQuery) {
+    constructor(getAutocompleterComponent, updateQuery, room) {
         this._autoCompleteCreator = (updateCallback) => {
-            return new AutocompleteWrapperModel(updateCallback, getAutocompleterComponent, updateQuery);
+            return new AutocompleteWrapperModel(
+                updateCallback,
+                getAutocompleterComponent,
+                updateQuery,
+                room,
+            );
         };
     }