diff --git a/src/App.js b/src/App.js
index 46145b16a35b02417dc39b44c10207f81f403c65..e72c73e35c413ceb3a6523aae2885ec496206dd0 100644
--- a/src/App.js
+++ b/src/App.js
@@ -10,6 +10,7 @@ import MediaModal from './components/media_modal/media_modal.vue'
 import SideDrawer from './components/side_drawer/side_drawer.vue'
 import MobilePostStatusModal from './components/mobile_post_status_modal/mobile_post_status_modal.vue'
 import MobileNav from './components/mobile_nav/mobile_nav.vue'
+import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
 import { windowWidth } from './services/window_utils/window_utils'
 
 export default {
@@ -26,7 +27,8 @@ export default {
     MediaModal,
     SideDrawer,
     MobilePostStatusModal,
-    MobileNav
+    MobileNav,
+    UserReportingModal
   },
   data: () => ({
     mobileActivePanel: 'timeline',
diff --git a/src/App.vue b/src/App.vue
index 3b8623ad77195bcef3bb00d8f20bd15318475bce..cb7e8d785ea23ca937eef3fd96e5a4ad82b85919 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -46,6 +46,7 @@
       <media-modal></media-modal>
     </div>
     <chat-panel :floating="true" v-if="currentUser && chat" class="floating-chat mobile-hidden"></chat-panel>
+    <UserReportingModal />
   </div>
 </template>
 
diff --git a/src/components/checkbox/checkbox.js b/src/components/checkbox/checkbox.js
new file mode 100644
index 0000000000000000000000000000000000000000..324a7597d6cc7998bbb7b473277ff7b5db9f22b9
--- /dev/null
+++ b/src/components/checkbox/checkbox.js
@@ -0,0 +1,44 @@
+// TODO: Template-based functional component is supported in vue-loader 13.3.0+.
+// Also, somehow, props are not provided through 'context' even though they are defined.
+// Need to upgrade vue-loader
+
+import './checkbox.scss'
+
+export default {
+  functional: true,
+  name: 'Checkbox',
+  model: {
+    prop: 'checked',
+    event: 'change'
+  },
+  render (createElement, { data, children }) {
+    const { props = {}, attrs = {}, on = {}, ...rest } = data
+    const { name, checked, disabled, readonly, ...restAttrs } = attrs
+    const { change, ...restListeners } = on
+    const wrapperProps = {
+      attrs: restAttrs,
+      on: restListeners,
+      ...rest
+    }
+    const inputProps = {
+      attrs: {
+        name,
+        checked,
+        disabled,
+        readonly,
+        ...props
+      },
+      on: {}
+    }
+    if (change) {
+      inputProps.on.change = e => change(e.target.checked)
+    }
+    return (
+      <label class="checkbox" {...wrapperProps}>
+        <input type="checkbox" {...inputProps} />
+        <i />
+        {children && <span>{children}</span>}
+      </label>
+    )
+  }
+}
diff --git a/src/components/checkbox/checkbox.scss b/src/components/checkbox/checkbox.scss
new file mode 100644
index 0000000000000000000000000000000000000000..07b3f39e1ddaea547fdb615ec2dbe93f9b58be59
--- /dev/null
+++ b/src/components/checkbox/checkbox.scss
@@ -0,0 +1,48 @@
+@import '../../_variables.scss';
+
+.checkbox {
+  position: relative;
+  display: inline-block;
+  padding-left: 1.2em;
+
+  input[type=checkbox] {
+    display: none;
+
+    & + i::before {
+      position: absolute;
+      left: 0;
+      top: 0;
+      display: block;
+      content: '✔';
+      transition: color 200ms;
+      width: 1.1em;
+      height: 1.1em;
+      border-radius: $fallback--checkboxRadius;
+      border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
+      box-shadow: 0px 0px 2px black inset;
+      box-shadow: var(--inputShadow);
+      background-color: $fallback--fg;
+      background-color: var(--input, $fallback--fg);
+      vertical-align: top;
+      text-align: center;
+      line-height: 1.1em;
+      font-size: 1.1em;
+      color: transparent;
+      overflow: hidden;
+      box-sizing: border-box;
+    }
+
+    &:checked + i::before {
+      color: $fallback--text;
+      color: var(--text, $fallback--text);
+    }
+
+    &:disabled + i::before {
+      opacity: .5;
+    }
+  }
+
+  & > span {
+    margin-left: .5em;
+  }
+}
\ No newline at end of file
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 1a100de398ba3715157ca35a32645027f7738416..7c6ffa8986fbbf2bbf59bbe428e845a1f39b4bf5 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -151,6 +151,9 @@ export default {
     },
     userProfileLink (user) {
       return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
+    },
+    reportUser () {
+      this.$store.dispatch('openUserReportingModal', this.user.id)
     }
   }
 }
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index e62b384d201555330f04654bd615e6843017ac9c..2d02ca0300e6c459804d5b5add55057cdae65edb 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -99,8 +99,14 @@
             </button>
           </span>
         </div>
-        <ModerationTools :user='user' v-if='loggedIn.role === "admin"'>
-        </ModerationTools>
+        <div class='block' v-if='isOtherUser && loggedIn'>
+          <span>
+            <button @click="reportUser">
+              {{ $t('user_card.report') }}
+            </button>
+          </span>
+        </div>
+        <ModerationTools :user='user' v-if='loggedIn.role === "admin"'/>
       </div>
     </div>
   </div>
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
new file mode 100644
index 0000000000000000000000000000000000000000..fb9ea16da8ccc271cf861c81274bbc3c8994bb98
--- /dev/null
+++ b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -0,0 +1,81 @@
+
+import Status from '../status/status.vue'
+import Checkbox from '../checkbox/checkbox.js'
+
+const UserReportingModal = {
+  components: {
+    Status,
+    Checkbox
+  },
+  data () {
+    return {
+      comment: '',
+      forward: false,
+      statusIdsToReport: []
+    }
+  },
+  computed: {
+    isLoggedIn () {
+      return !!this.$store.state.users.currentUser
+    },
+    isOpen () {
+      return this.isLoggedIn && this.$store.state.reports.modalActivated
+    },
+    userId () {
+      return this.$store.state.reports.userId
+    },
+    user () {
+      return this.$store.getters.findUser(this.userId)
+    },
+    remoteInstance () {
+      return !this.user.is_local && this.user.screen_name.substr(this.user.screen_name.indexOf('@') + 1)
+    },
+    statuses () {
+      return this.$store.state.reports.statuses
+    }
+  },
+  watch: {
+    userId (value) {
+      this.statusIdsToReport = []
+    }
+  },
+  methods: {
+    closeModal () {
+      this.$store.dispatch('closeUserReportingModal')
+    },
+    reportUser () {
+      const payload = {
+        comment: this.comment,
+        forward: this.forward,
+        statusIdsToReport: this.statusIdsToReport
+      }
+      this.$store.dispatch('reportUser', payload)
+    },
+    isChecked (statusId) {
+      return this.statusIdsToReport.indexOf(statusId) !== -1
+    },
+    toggleStatus (checked, statusId) {
+      if (checked === this.isChecked(statusId)) {
+        return
+      }
+
+      if (checked) {
+        this.statusIdsToReport.push(statusId)
+      } else {
+        this.statusIdsToReport.splice(this.statusIdsToReport.indexOf(statusId), 1)
+      }
+    },
+    resize (e) {
+      const target = e.target || e
+      if (!(target instanceof window.Element)) { return }
+      // Auto is needed to make textbox shrink when removing lines
+      target.style.height = 'auto'
+      target.style.height = `${target.scrollHeight}px`
+      if (target.value === '') {
+        target.style.height = null
+      }
+    }
+  }
+}
+
+export default UserReportingModal
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..49839da3a4174d93338d3cca167fd69ff73b5d93
--- /dev/null
+++ b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -0,0 +1,111 @@
+<template>
+<div class="modal-view" @click="closeModal" v-if="isOpen">
+  <div class="user-reporting-panel panel" @click.stop="">
+    <div class="panel-heading">Reporting {{user.screen_name}}</div>
+    <div class="panel-body">
+      <div class="user-reporting-panel-left">
+        <div>
+          <p>The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:</p>
+          <textarea
+            v-model="comment"
+            class="form-control"
+            placeholder="Additional comments"
+            rows="1"
+            @input="resize"
+          />
+        </div>
+        <div v-if="!user.is_local">
+          <p>The account is from another server. Send an anonymized copy of the report there as well?</p>
+          <Checkbox v-model="forward">Forward to {{remoteInstance}}</Checkbox>
+        </div>
+        <div>
+          <button class="btn btn-default" @click="reportUser">Submit</button>
+        </div>
+      </div>
+      <div class="user-reporting-panel-right">
+        <div v-for="status in statuses" :key="status.id" class="status-fadein">
+          <Status :inConversation="false" :focused="false" :statusoid="status" />
+          <Checkbox :checked="isChecked(status.id)" @change="checked => toggleStatus(checked, status.id)" />
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+</template>
+
+<script src="./user_reporting_modal.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.user-reporting-panel {
+  width: 90vw;
+  max-width: 700px;
+
+  .panel-body {
+    display: flex;
+    border-top: 1px solid;
+    border-color: $fallback--border;
+    border-color: var(--border, $fallback--border);
+  }
+
+  &-left {
+    width: 50%;
+    padding: 1.1em;
+    border-right: 1px solid;
+    border-color: $fallback--border;
+    border-color: var(--border, $fallback--border);
+    max-width: 320px;
+    line-height: 1.4em;
+    box-sizing: border-box;
+
+    > div {
+      margin-bottom: 2em;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+
+    p {
+      margin-top: 0;
+    }
+
+    textarea.form-control {
+      line-height: 16px;
+      resize: none;
+      overflow: hidden;
+      transition: min-height 200ms 100ms;
+      min-height: 44px;
+      width: 100%;
+    }
+
+    .btn {
+      min-width: 10em;
+      padding: 0 2em;
+    }
+  }
+
+  &-right {
+    width: 50%;
+    flex: 1 1 auto;
+    min-height: 20vh;
+    max-height: 80vh;
+    overflow-y: auto;
+    overflow-x: hidden;
+
+    > div {
+      display: flex;
+      justify-content: space-between;
+      border-bottom-width: 1px;
+      border-bottom-style: solid;
+      border-color: $fallback--border;
+      border-color: var(--border, $fallback--border);
+
+      .checkbox {
+        margin: 0.75em;
+      }
+    }
+  }
+}
+</style>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index b07af5e5d9acb7f0f4a4571fd9caab293fcbc3b4..8292c921b94048d916234081d6dcfafc113d5f42 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -420,6 +420,7 @@
     "muted": "Muted",
     "per_day": "per day",
     "remote_follow": "Remote follow",
+    "report": "Report",
     "statuses": "Statuses",
     "unblock": "Unblock",
     "unblock_progress": "Unblocking...",
diff --git a/src/main.js b/src/main.js
index 725f5806583e392dab1e4f2ab15a73e393edb049..92f843b1b221d3fa40d972e5df1e8a64b4f83161 100644
--- a/src/main.js
+++ b/src/main.js
@@ -12,6 +12,7 @@ import chatModule from './modules/chat.js'
 import oauthModule from './modules/oauth.js'
 import mediaViewerModule from './modules/media_viewer.js'
 import oauthTokensModule from './modules/oauth_tokens.js'
+import reportsModule from './modules/reports.js'
 
 import VueTimeago from 'vue-timeago'
 import VueI18n from 'vue-i18n'
@@ -75,7 +76,8 @@ const persistedStateOptions = {
       chat: chatModule,
       oauth: oauthModule,
       mediaViewer: mediaViewerModule,
-      oauthTokens: oauthTokensModule
+      oauthTokens: oauthTokensModule,
+      reports: reportsModule
     },
     plugins: [persistedState, pushNotifications],
     strict: false // Socket modifies itself, let's ignore this for now.
diff --git a/src/modules/reports.js b/src/modules/reports.js
new file mode 100644
index 0000000000000000000000000000000000000000..b712cfebf046134d730fac36e8b3009f6c96621b
--- /dev/null
+++ b/src/modules/reports.js
@@ -0,0 +1,33 @@
+import filter from 'lodash/filter'
+
+const reports = {
+  state: {
+    userId: null,
+    statuses: [],
+    modalActivated: false
+  },
+  mutations: {
+    openUserReportingModal (state, { userId, statuses }) {
+      state.userId = userId
+      state.statuses = statuses
+      state.modalActivated = true
+    },
+    closeUserReportingModal (state) {
+      state.modalActivated = false
+    }
+  },
+  actions: {
+    openUserReportingModal ({ rootState, commit }, userId) {
+      const statuses = filter(rootState.statuses.allStatuses, status => status.user.id === userId)
+      commit('openUserReportingModal', { userId, statuses })
+    },
+    closeUserReportingModal ({ commit }) {
+      commit('closeUserReportingModal')
+    },
+    reportUser ({ commit }, payload) {
+      console.log('payload', payload)
+    }
+  }
+}
+
+export default reports