diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx
index 1bf95eec000e31b1f33c894d3534615993adfc10..e27b606eee9a18fb191bf795c1fc1a443bf9f00b 100644
--- a/app/assets/javascripts/components/actions/compose.jsx
+++ b/app/assets/javascripts/components/actions/compose.jsx
@@ -13,6 +13,9 @@ export const COMPOSE_UPLOAD_FAIL     = 'COMPOSE_UPLOAD_FAIL';
 export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
 export const COMPOSE_UPLOAD_UNDO     = 'COMPOSE_UPLOAD_UNDO';
 
+export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
+export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
+
 export function changeCompose(text) {
   return {
     type: COMPOSE_CHANGE,
@@ -129,3 +132,27 @@ export function undoUploadCompose(media_id) {
     media_id: media_id
   };
 };
+
+export function clearComposeSuggestions() {
+  return {
+    type: COMPOSE_SUGGESTIONS_CLEAR
+  };
+};
+
+export function fetchComposeSuggestions(token) {
+  return (dispatch, getState) => {
+    const loadedCandidates = getState().get('accounts').filter(item => item.get('acct').toLowerCase().slice(0, token.length) === token).map(item => ({
+      label: item.get('acct'),
+      completion: item.get('acct').slice(0, token.length)
+    })).toList().toJS();
+
+    dispatch(readyComposeSuggestions(loadedCandidates));
+  };
+};
+
+export function readyComposeSuggestions(accounts) {
+  return {
+    type: COMPOSE_SUGGESTIONS_READY,
+    accounts
+  };
+};
diff --git a/app/assets/javascripts/components/features/ui/components/compose_form.jsx b/app/assets/javascripts/components/features/ui/components/compose_form.jsx
index 5b00fc1b933575a845089b9ccfa6edd7e54e16e7..464423cf845d6978690f22476f4889b5ada0995b 100644
--- a/app/assets/javascripts/components/features/ui/components/compose_form.jsx
+++ b/app/assets/javascripts/components/features/ui/components/compose_form.jsx
@@ -4,11 +4,62 @@ import PureRenderMixin    from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ReplyIndicator     from './reply_indicator';
 import UploadButton       from './upload_button';
+import Autosuggest        from 'react-autosuggest';
+
+const getTokenForSuggestions = (str, caretPosition) => {
+  let word;
+
+  let left  = str.slice(0, caretPosition).search(/\S+$/);
+  let right = str.slice(caretPosition).search(/\s/);
+
+  if (right < 0) {
+    word = str.slice(left);
+  } else {
+    word = str.slice(left, right + caretPosition);
+  }
+
+  if (!word || word.trim().length < 2 || word[0] !== '@') {
+    return null;
+  }
+
+  word = word.trim().toLowerCase().slice(1);
+
+  if (word.length > 0) {
+    return word;
+  } else {
+    return null;
+  }
+};
+
+const getSuggestionValue = suggestion => suggestion;
+
+const renderSuggestion = suggestion => (
+  <span>{suggestion}</span>
+);
+
+const textareaStyle = {
+  display: 'block',
+  boxSizing: 'border-box',
+  width: '100%',
+  height: '100px',
+  resize: 'none',
+  border: 'none',
+  color: '#282c37',
+  padding: '10px',
+  fontFamily: 'Roboto',
+  fontSize: '14px',
+  margin: '0'
+};
+
+const renderInputComponent = inputProps => (
+  <textarea {...inputProps} placeholder='What is on your mind?'  className='compose-form__textarea' style={textareaStyle} />
+);
 
 const ComposeForm = React.createClass({
 
   propTypes: {
     text: React.PropTypes.string.isRequired,
+    suggestions: React.PropTypes.array,
     is_submitting: React.PropTypes.bool,
     is_uploading: React.PropTypes.bool,
     in_reply_to: ImmutablePropTypes.map,
@@ -35,7 +86,39 @@ const ComposeForm = React.createClass({
 
   componentDidUpdate (prevProps) {
     if (prevProps.text !== this.props.text || prevProps.in_reply_to !== this.props.in_reply_to) {
-      this.refs.textarea.focus();
+      const node     = ReactDOM.findDOMNode(this.refs.autosuggest);
+      const textarea = node.querySelector('textarea');
+
+      if (textarea) {
+        textarea.focus();
+      }
+    }
+  },
+
+  onSuggestionsClearRequested () {
+    this.props.onClearSuggestions();
+  },
+
+  onSuggestionsFetchRequested ({ value }) {
+    const node     = ReactDOM.findDOMNode(this.refs.autosuggest);
+    const textarea = node.querySelector('textarea');
+
+    if (textarea) {
+      const token = getTokenForSuggestions(value, textarea.selectionStart);
+
+      if (token !== null) {
+        this.props.onFetchSuggestions(token);
+      }
+    }
+  },
+
+  onSuggestionSelected (e, { suggestionValue, method }) {
+    const node     = ReactDOM.findDOMNode(this.refs.autosuggest);
+    const textarea = node.querySelector('textarea');
+
+    if (textarea) {
+      const str = this.props.text;
+      this.props.onChange([str.slice(0, textarea.selectionStart), suggestionValue, str.slice(textarea.selectionStart)].join(''));
     }
   },
 
@@ -47,11 +130,29 @@ const ComposeForm = React.createClass({
       replyArea = <ReplyIndicator status={this.props.in_reply_to} onCancel={this.props.onCancelReply} />;
     }
 
+    const inputProps = {
+      placeholder: 'What is on your mind?',
+      value: this.props.text,
+      onKeyUp: this.handleKeyUp,
+      onChange: this.handleChange,
+      disabled: disabled
+    };
+
     return (
       <div style={{ padding: '10px' }}>
         {replyArea}
 
-        <textarea ref='textarea' disabled={disabled} placeholder='What is on your mind?' value={this.props.text} onKeyUp={this.handleKeyUp} onChange={this.handleChange} className='compose-form__textarea' style={{ display: 'block', boxSizing: 'border-box', width: '100%', height: '100px', resize: 'none', border: 'none', color: '#282c37', padding: '10px', fontFamily: 'Roboto', fontSize: '14px', margin: '0' }} />
+        <Autosuggest
+          ref='autosuggest'
+          suggestions={this.props.suggestions}
+          onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
+          onSuggestionsClearRequested={this.onSuggestionsClearRequested}
+          onSuggestionSelected={this.onSuggestionSelected}
+          getSuggestionValue={getSuggestionValue}
+          renderSuggestion={renderSuggestion}
+          renderInputComponent={renderInputComponent}
+          inputProps={inputProps}
+        />
 
         <div style={{ marginTop: '10px', overflow: 'hidden' }}>
           <div style={{ float: 'right' }}><Button text='Publish' onClick={this.handleSubmit} disabled={disabled} /></div>
diff --git a/app/assets/javascripts/components/features/ui/components/upload_button.jsx b/app/assets/javascripts/components/features/ui/components/upload_button.jsx
index d1b093242c8da92d29b845bb83c269fc0fc270d5..9e9fc72981314c07c1c79e46c5443a08781cec4b 100644
--- a/app/assets/javascripts/components/features/ui/components/upload_button.jsx
+++ b/app/assets/javascripts/components/features/ui/components/upload_button.jsx
@@ -24,7 +24,7 @@ const UploadButton = React.createClass({
     return (
       <div>
         <Button disabled={this.props.disabled} onClick={this.handleClick} block={true}>
-          <i className='fa fa-fw fa-photo' /> Add images
+          <i className='fa fa-fw fa-photo' /> Add media
         </Button>
 
         <input ref='fileElement' type='file' multiple={false} onChange={this.handleChange} disabled={this.props.disabled} style={{ display: 'none' }} />
diff --git a/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx
index 163d6fa2082357d1754cb4be6b034c3b9fda8023..dcfeef752e9b91b446e71c75b0ebaea007026b4c 100644
--- a/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx
@@ -1,7 +1,13 @@
-import { connect }                                          from 'react-redux';
-import ComposeForm                                          from '../components/compose_form';
-import { changeCompose, submitCompose, cancelReplyCompose } from '../../../actions/compose';
-import { makeGetStatus }                                    from '../../../selectors';
+import { connect } from 'react-redux';
+import ComposeForm from '../components/compose_form';
+import {
+  changeCompose,
+  submitCompose,
+  cancelReplyCompose,
+  clearComposeSuggestions,
+  fetchComposeSuggestions
+} from '../../../actions/compose';
+import { makeGetStatus } from '../../../selectors';
 
 const makeMapStateToProps = () => {
   const getStatus = makeGetStatus();
@@ -9,6 +15,7 @@ const makeMapStateToProps = () => {
   const mapStateToProps = function (state, props) {
     return {
       text: state.getIn(['compose', 'text']),
+      suggestions: state.getIn(['compose', 'suggestions']),
       is_submitting: state.getIn(['compose', 'is_submitting']),
       is_uploading: state.getIn(['compose', 'is_uploading']),
       in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to']))
@@ -20,16 +27,24 @@ const makeMapStateToProps = () => {
 
 const mapDispatchToProps = function (dispatch) {
   return {
-    onChange: function (text) {
+    onChange (text) {
       dispatch(changeCompose(text));
     },
 
-    onSubmit: function () {
+    onSubmit () {
       dispatch(submitCompose());
     },
 
-    onCancelReply: function () {
+    onCancelReply () {
       dispatch(cancelReplyCompose());
+    },
+
+    onClearSuggestions () {
+      dispatch(clearComposeSuggestions());
+    },
+
+    onFetchSuggestions (token) {
+      dispatch(fetchComposeSuggestions(token));
     }
   }
 };
diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx
index 1c676326fcf89128b6e6a770957235d14fa09d39..85799bf015d9a75b51ebe78bcbe2d47eba53bf74 100644
--- a/app/assets/javascripts/components/reducers/compose.jsx
+++ b/app/assets/javascripts/components/reducers/compose.jsx
@@ -10,7 +10,9 @@ import {
   COMPOSE_UPLOAD_SUCCESS,
   COMPOSE_UPLOAD_FAIL,
   COMPOSE_UPLOAD_UNDO,
-  COMPOSE_UPLOAD_PROGRESS
+  COMPOSE_UPLOAD_PROGRESS,
+  COMPOSE_SUGGESTIONS_CLEAR,
+  COMPOSE_SUGGESTIONS_READY
 } from '../actions/compose';
 import { TIMELINE_DELETE } from '../actions/timelines';
 import { ACCOUNT_SET_SELF } from '../actions/accounts';
@@ -22,7 +24,8 @@ const initialState = Immutable.Map({
   is_submitting: false,
   is_uploading: false,
   progress: 0,
-  media_attachments: Immutable.List([]),
+  media_attachments: Immutable.List(),
+  suggestions: [],
   me: null
 });
 
@@ -95,6 +98,10 @@ export default function compose(state = initialState, action) {
       return state.set('progress', Math.round((action.loaded / action.total) * 100));
     case COMPOSE_MENTION:
       return state.update('text', text => `${text}@${action.account.get('acct')} `);
+    case COMPOSE_SUGGESTIONS_CLEAR:
+      return state.set('suggestions', []);
+    case COMPOSE_SUGGESTIONS_READY:
+      return state.set('suggestions', action.accounts);
     case TIMELINE_DELETE:
       if (action.id === state.get('in_reply_to')) {
         return state.set('in_reply_to', null);
diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss
index 2b1c1194d9b3b9e4036fb72f29dca46ae54f8b1f..b720848c544974e53a780173a2ef25a54329ef11 100644
--- a/app/assets/stylesheets/components.scss
+++ b/app/assets/stylesheets/components.scss
@@ -266,3 +266,31 @@
     flex-direction: column;
   }
 }
+
+.react-autosuggest__container {
+  position: relative;
+}
+
+.react-autosuggest__suggestions-container {
+  position: absolute;
+  top: 100%;
+  width: 100%;
+  z-index: 99;
+}
+
+.react-autosuggest__suggestions-list {
+  background: #9baec8;
+  color: #282c37;
+  box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
+  font-size: 14px;
+}
+
+.react-autosuggest__suggestion {
+  padding: 10px;
+  cursor: pointer;
+}
+
+.react-autosuggest__suggestion--focused {
+  background: #2b90d9;
+  color: #fff;
+}
diff --git a/package.json b/package.json
index 558e565415af41e2140bb201ba85f3f16a9452d6..e17fbef525d995b9f75bf635df22b155aaa8cdbc 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
     "sinon": "^1.17.6"
   },
   "dependencies": {
+    "react-autosuggest": "^7.0.1",
     "react-responsive": "^1.1.5",
     "react-router-scroll": "^0.3.2",
     "react-skylight": "^0.4.1"
diff --git a/yarn.lock b/yarn.lock
index 766ca3114268174a512c0f4184ef06be756fddf9..75baca8877f81e979188ff189bdd883cabf48199 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3243,6 +3243,10 @@ oauth-sign@~0.8.1:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
 
+object-assign@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
+
 object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
@@ -3807,6 +3811,22 @@ react-addons-pure-render-mixin@^15.3.1:
   version "15.3.2"
   resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.3.2.tgz#c09a44f583425a4a9c1b38444d7a6c3e6f0f41f6"
 
+react-autosuggest:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-7.0.1.tgz#e751d2c2e516a344f6cdc150672e85f134f5f2f1"
+  dependencies:
+    react-autowhatever "^7.0.0"
+    react-redux "^4.4.5"
+    redux "^3.6.0"
+    shallow-equal "^1.0.0"
+
+react-autowhatever@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/react-autowhatever/-/react-autowhatever-7.0.0.tgz#7ea19f8024183acf1568fc8e4b76c0d0cc250d00"
+  dependencies:
+    react-themeable "^1.1.0"
+    section-iterator "^2.0.0"
+
 react-deep-force-update@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.0.1.tgz#f911b5be1d2a6fe387507dd6e9a767aa2924b4c7"
@@ -3878,6 +3898,15 @@ react-redux-loading-bar@^2.3.3:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/react-redux-loading-bar/-/react-redux-loading-bar-2.4.0.tgz#00cd884c7ea8e0146fb94aeb1435b1a0caffd888"
 
+react-redux@^4.4.5:
+  version "4.4.5"
+  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-4.4.5.tgz#f509a2981be2252d10c629ef7c559347a4aec457"
+  dependencies:
+    hoist-non-react-statics "^1.0.3"
+    invariant "^2.0.0"
+    lodash "^4.2.0"
+    loose-envify "^1.1.0"
+
 react-redux@^5.0.0-beta.3:
   version "5.0.0-beta.3"
   resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.0-beta.3.tgz#d50bfb00799cf7d2a9fd55fe34d6b3ecc24d3072"
@@ -3931,6 +3960,12 @@ react-skylight:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/react-skylight/-/react-skylight-0.4.1.tgz#07d1af6dea0a50a5d8122a786a8ce8bc6bdf2241"
 
+react-themeable@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/react-themeable/-/react-themeable-1.1.0.tgz#7d4466dd9b2b5fa75058727825e9f152ba379a0e"
+  dependencies:
+    object-assign "^3.0.0"
+
 react@^15.3.2:
   version "15.3.2"
   resolved "https://registry.yarnpkg.com/react/-/react-15.3.2.tgz#a7bccd2fee8af126b0317e222c28d1d54528d09e"
@@ -4033,7 +4068,7 @@ redux-thunk@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.1.0.tgz#c724bfee75dbe352da2e3ba9bc14302badd89a98"
 
-redux@^3.5.2:
+redux@^3.5.2, redux@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d"
   dependencies:
@@ -4170,6 +4205,10 @@ scroll-behavior@^0.8.0:
     dom-helpers "^2.4.0"
     invariant "^2.2.1"
 
+section-iterator@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a"
+
 semver@~5.3.0:
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
@@ -4232,6 +4271,10 @@ sha.js@2.2.6:
   version "2.2.6"
   resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba"
 
+shallow-equal@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.0.0.tgz#508d1838b3de590ab8757b011b25e430900945f7"
+
 shallowequal@0.2.x:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-0.2.2.tgz#1e32fd5bcab6ad688a4812cb0cc04efc75c7014e"