diff --git a/app/javascript/flavours/glitch/components/status_action_bar.jsx b/app/javascript/flavours/glitch/components/status_action_bar.jsx
index 1e5bb964f3..8a291671e3 100644
--- a/app/javascript/flavours/glitch/components/status_action_bar.jsx
+++ b/app/javascript/flavours/glitch/components/status_action_bar.jsx
@@ -121,10 +121,6 @@ class StatusActionBar extends ImmutablePureComponent {
     this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
   }
 
-  handleEmojiPick = data => {
-    this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
-  }
-
   handleReblogClick = e => {
     const { signedIn } = this.context.identity;
 
@@ -208,8 +204,6 @@ class StatusActionBar extends ImmutablePureComponent {
 
   handleNoOp = () => {} // hack for reaction add button
 
-  handleNoOp = () => {} // hack for reaction add button
-
   render () {
     const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props;
     const { permissions } = this.context.identity;
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.jsx b/app/javascript/flavours/glitch/features/notifications/components/notification.jsx
index 13892dbb53..b455eba632 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/notification.jsx
+++ b/app/javascript/flavours/glitch/features/notifications/components/notification.jsx
@@ -179,28 +179,6 @@ export default class Notification extends ImmutablePureComponent {
           unread={this.props.unread}
         />
       );
-    case 'reaction':
-      return (
-        <StatusContainer
-          containerId={notification.get('id')}
-          hidden={hidden}
-          id={notification.get('status')}
-          account={notification.get('account')}
-          prepend='reaction'
-          muted
-          notification={notification}
-          onMoveDown={onMoveDown}
-          onMoveUp={onMoveUp}
-          onMention={onMention}
-          getScrollPosition={getScrollPosition}
-          updateScrollBottom={updateScrollBottom}
-          cachedMediaWidth={this.props.cachedMediaWidth}
-          cacheMediaWidth={this.props.cacheMediaWidth}
-          onUnmount={this.props.onUnmount}
-          withDismiss
-          unread={this.props.unread}
-        />
-      );
     case 'reblog':
       return (
         <StatusContainer
diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx
index d8507bda6e..30c198d6de 100644
--- a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx
+++ b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx
@@ -84,10 +84,6 @@ class ActionBar extends React.PureComponent {
     this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
   }
 
-  handleEmojiPick = data => {
-    this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
-  }
-
   handleBookmarkClick = (e) => {
     this.props.onBookmark(this.props.status, e);
   };
@@ -150,8 +146,6 @@ class ActionBar extends React.PureComponent {
 
   handleNoOp = () => {} // hack for reaction add button
 
-  handleNoOp = () => {} // hack for reaction add button
-
   render () {
     const { status, intl } = this.props;
     const { signedIn, permissions } = this.context.identity;
diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx
index 16e407bfdf..1e140bd495 100644
--- a/app/javascript/flavours/glitch/features/status/index.jsx
+++ b/app/javascript/flavours/glitch/features/status/index.jsx
@@ -309,19 +309,6 @@ class Status extends ImmutablePureComponent {
     this.props.dispatch(removeReaction(statusId, name));
   }
 
-  handleReactionAdd = (statusId, name, url) => {
-    const { dispatch } = this.props;
-    const { signedIn } = this.context.identity;
-
-    if (signedIn) {
-      dispatch(addReaction(statusId, name, url));
-    }
-  }
-
-  handleReactionRemove = (statusId, name) => {
-    this.props.dispatch(removeReaction(statusId, name));
-  }
-
   handlePin = (status) => {
     if (status.get('pinned')) {
       this.props.dispatch(unpin(status));
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index fcfdeaad54..421e0d884e 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -383,10 +383,6 @@
   .reactions-bar--empty {
     display: none;
   }
-
-  .reactions-bar--empty {
-    display: none;
-  }
 }
 
 .notification-favourite {
diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx
index b14302d157..cf6e0455f7 100644
--- a/app/javascript/mastodon/components/status_action_bar.jsx
+++ b/app/javascript/mastodon/components/status_action_bar.jsx
@@ -133,10 +133,6 @@ class StatusActionBar extends ImmutablePureComponent {
       this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
   }
 
-  handleEmojiPick = data => {
-      this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
-  }
-
   handleReblogClick = e => {
     const { signedIn } = this.context.identity;
 
@@ -242,8 +238,6 @@ class StatusActionBar extends ImmutablePureComponent {
 
   handleNoOp = () => {} // hack for reaction add button
 
-  handleNoOp = () => {} // hack for reaction add button
-
   render () {
     const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
     const { signedIn, permissions } = this.context.identity;
diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx
index 33adb2a839..32078e05ac 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.jsx
+++ b/app/javascript/mastodon/features/status/components/action_bar.jsx
@@ -97,10 +97,6 @@ class ActionBar extends React.PureComponent {
     this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''));
   }
 
-  handleEmojiPick = data => {
-    this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''));
-  }
-
   handleBookmarkClick = (e) => {
     this.props.onBookmark(this.props.status, e);
   };
@@ -191,8 +187,6 @@ class ActionBar extends React.PureComponent {
 
   handleNoOp = () => {} // hack for reaction add button
 
-  handleNoOp = () => {} // hack for reaction add button
-
   render () {
     const { status, relationship, intl } = this.props;
     const { signedIn, permissions } = this.context.identity;
diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx
index f8cc88e1d6..5cb1014890 100644
--- a/app/javascript/mastodon/features/status/index.jsx
+++ b/app/javascript/mastodon/features/status/index.jsx
@@ -268,19 +268,6 @@ class Status extends ImmutablePureComponent {
     this.props.dispatch(removeReaction(statusId, name));
   }
 
-  handleReactionAdd = (statusId, name, url) => {
-    const { dispatch } = this.props;
-    const { signedIn } = this.context.identity;
-
-    if (signedIn) {
-      dispatch(addReaction(statusId, name, url));
-    }
-  }
-
-  handleReactionRemove = (statusId, name) => {
-    this.props.dispatch(removeReaction(statusId, name));
-  }
-
   handlePin = (status) => {
     if (status.get('pinned')) {
       this.props.dispatch(unpin(status));
diff --git a/app/models/concerns/has_user_settings.rb b/app/models/concerns/has_user_settings.rb
index 0e9d4e1cd4..bc6af2d361 100644
--- a/app/models/concerns/has_user_settings.rb
+++ b/app/models/concerns/has_user_settings.rb
@@ -127,6 +127,10 @@ module HasUserSettings
     settings['hide_followers_count']
   end
 
+  def setting_visible_reactions
+    integer_cast_setting('visible_reactions', 0)
+  end
+
   def allows_report_emails?
     settings['notification_emails.report']
   end
@@ -170,4 +174,14 @@ module HasUserSettings
   def hide_all_media?
     settings['web.display_media'] == 'hide_all'
   end
+
+  def integer_cast_setting(key, min = nil, max = nil)
+    i = ActiveModel::Type::Integer.new.cast(settings[key])
+    # the cast above doesn't return a number if passed the string "e"
+    i = 0 unless i.is_a? Numeric
+    return min if !min.nil? && i < min
+    return max if !max.nil? && i > max
+
+    i
+  end
 end
diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb
index 0be8c5fbce..43afe32add 100644
--- a/app/models/user_settings.rb
+++ b/app/models/user_settings.rb
@@ -18,6 +18,7 @@ class UserSettings
   setting :default_privacy, default: nil
   setting :default_content_type, default: 'text/plain'
   setting :hide_followers_count, default: false
+  setting :visible_reactions, default: 6
 
   namespace :web do
     setting :crop_images, default: true
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index dc82ce5b60..9751da546f 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -39,6 +39,9 @@
     .fields-group
       = ff.input :'web.crop_images', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_crop_images')
 
+    .fields-group.fields-row__column.fields-row__column-6
+      = ff.input :'visible_reactions', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_visible_reactions'), input_html: { type: 'number', min: '0', data: { default: '6' } }, hint: false
+
     %h4= t 'appearance.discovery'
 
     .fields-group
diff --git a/db/migrate/20230215074425_move_emoji_reaction_settings.rb b/db/migrate/20230215074425_move_emoji_reaction_settings.rb
new file mode 100644
index 0000000000..9b9a65e046
--- /dev/null
+++ b/db/migrate/20230215074425_move_emoji_reaction_settings.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+class MoveEmojiReactionSettings < ActiveRecord::Migration[6.1]
+  class User < ApplicationRecord; end
+
+  MAPPING = {
+    setting_visible_reactions: 'visible_reactions',
+  }.freeze
+
+  class LegacySetting < ApplicationRecord
+    self.table_name = 'settings'
+
+    def var
+      self[:var]&.to_sym
+    end
+
+    def value
+      YAML.safe_load(self[:value], permitted_classes: [ActiveSupport::HashWithIndifferentAccess]) if self[:value].present?
+    end
+  end
+
+  def up
+    User.find_each do |user|
+      previous_settings = LegacySetting.where(thing_type: 'User', thing_id: user.id).index_by(&:var)
+
+      user_settings = Oj.load(user.settings || '{}')
+      user_settings.delete('theme')
+
+      MAPPING.each do |legacy_key, new_key|
+        value = previous_settings[legacy_key]&.value
+
+        next if value.blank?
+
+        if value.is_a?(Hash)
+          value.each do |nested_key, nested_value|
+            user_settings[MAPPING[legacy_key][nested_key.to_sym]] = nested_value
+          end
+        else
+          user_settings[new_key] = value
+        end
+      end
+
+      user.update_column('settings', Oj.dump(user_settings)) # rubocop:disable Rails/SkipsModelValidations
+    end
+  end
+
+  def down; end
+end
diff --git a/db/schema.rb b/db/schema.rb
index dc2b5af137..c35a7ae9f4 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2023_02_15_074424) do
+ActiveRecord::Schema.define(version: 2023_02_15_074425) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"