diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6
index 1ae4cfffd68..9b250b122b0 100644
--- a/app/assets/javascripts/discourse/controllers/composer.js.es6
+++ b/app/assets/javascripts/discourse/controllers/composer.js.es6
@@ -16,6 +16,7 @@ import {
 } from "discourse/lib/utilities";
 import { emojiUnescape } from "discourse/lib/text";
 import { shortDate } from "discourse/lib/formatter";
+import { SAVE_LABELS, SAVE_ICONS } from "discourse/models/composer";
 
 function loadDraft(store, opts) {
   opts = opts || {};
@@ -101,7 +102,7 @@ export default Ember.Controller.extend({
   linkLookup: null,
   showPreview: true,
   forcePreview: Ember.computed.and("site.mobileView", "showPreview"),
-  whisperOrUnlistTopic: Ember.computed.or("model.whisper", "model.unlistTopic"),
+  whisperOrUnlistTopic: Ember.computed.or("isWhispering", "model.unlistTopic"),
   categories: Ember.computed.alias("site.categoriesList"),
 
   @on("init")
@@ -200,15 +201,42 @@ export default Ember.Controller.extend({
 
   canUnlistTopic: Ember.computed.and("model.creatingTopic", "isStaffUser"),
 
-  @computed("canWhisper", "model.post")
-  showWhisperToggle(canWhisper, repliedToPost) {
-    const replyingToWhisper =
-      repliedToPost &&
-      repliedToPost.get("post_type") === this.site.post_types.whisper;
-
+  @computed("canWhisper", "replyingToWhisper")
+  showWhisperToggle(canWhisper, replyingToWhisper) {
     return canWhisper && !replyingToWhisper;
   },
 
+  @computed("model.post")
+  replyingToWhisper(repliedToPost) {
+    return (
+      repliedToPost &&
+      repliedToPost.get("post_type") === this.site.post_types.whisper
+    );
+  },
+
+  @computed("replyingToWhisper", "model.whisper")
+  isWhispering(replyingToWhisper, whisper) {
+    return replyingToWhisper || whisper;
+  },
+
+  @computed("model.action", "isWhispering")
+  saveIcon(action, isWhispering) {
+    if (isWhispering) {
+      return "eye-slash";
+    }
+    return SAVE_ICONS[action];
+  },
+
+  @computed("model.action", "isWhispering", "model.editConflict")
+  saveLabel(action, isWhispering, editConflict) {
+    if (editConflict) {
+      return "composer.overwrite_edit";
+    } else if (isWhispering) {
+      return "composer.create_whisper";
+    }
+    return SAVE_LABELS[action];
+  },
+
   @computed("isStaffUser", "model.action")
   canWhisper(isStaffUser, action) {
     return (
diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6
index 69a40d35f9e..e67e10515ff 100644
--- a/app/assets/javascripts/discourse/models/composer.js.es6
+++ b/app/assets/javascripts/discourse/models/composer.js.es6
@@ -52,7 +52,7 @@ const CLOSED = "closed",
     featuredLink: "topic.featured_link"
   };
 
-const SAVE_LABELS = {
+export const SAVE_LABELS = {
   [EDIT]: "composer.save_edit",
   [REPLY]: "composer.reply",
   [CREATE_TOPIC]: "composer.create_topic",
@@ -61,7 +61,7 @@ const SAVE_LABELS = {
   [EDIT_SHARED_DRAFT]: "composer.save_edit"
 };
 
-const SAVE_ICONS = {
+export const SAVE_ICONS = {
   [EDIT]: "pencil",
   [EDIT_SHARED_DRAFT]: "clipboard",
   [REPLY]: "reply",
@@ -385,24 +385,6 @@ const Composer = RestModel.extend({
     return this.get("titleLength") <= this.siteSettings.max_topic_title_length;
   }.property("minimumTitleLength", "titleLength", "post.static_doc"),
 
-  @computed("action", "whisper")
-  saveIcon(action, whisper) {
-    if (whisper) {
-      return "eye-slash";
-    }
-    return SAVE_ICONS[action];
-  },
-
-  @computed("action", "whisper", "editConflict")
-  saveLabel(action, whisper, editConflict) {
-    if (editConflict) {
-      return "composer.overwrite_edit";
-    } else if (whisper) {
-      return "composer.create_whisper";
-    }
-    return SAVE_LABELS[action];
-  },
-
   hasMetaData: function() {
     const metaData = this.get("metaData");
     return metaData ? Ember.isEmpty(Ember.keys(this.get("metaData"))) : false;
diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs
index a9c12751885..c991f58c214 100644
--- a/app/assets/javascripts/discourse/templates/composer.hbs
+++ b/app/assets/javascripts/discourse/templates/composer.hbs
@@ -25,7 +25,7 @@
                   {{plugin-outlet name="composer-action-after" noTags=true args=(hash model=model)}}
 
                   {{#unless site.mobileView}}
-                    {{#if model.whisper}}
+                    {{#if isWhispering}}
                       <span class='whisper'>{{d-icon 'eye-slash'}}</span>
                     {{/if}}
                     {{#if model.unlistTopic}}
@@ -119,8 +119,8 @@
             <div class='save-or-cancel'>
               {{#unless model.viewFullscreen}}
                 {{composer-save-button action=(action "save")
-                                       icon=model.saveIcon
-                                       label=model.saveLabel
+                                       icon=saveIcon
+                                       label=saveLabel
                                        disableSubmit=disableSubmit}}
 
               {{#if site.mobileView}}
diff --git a/test/javascripts/acceptance/composer-test.js.es6 b/test/javascripts/acceptance/composer-test.js.es6
index 2362f263448..4f80d96cd61 100644
--- a/test/javascripts/acceptance/composer-test.js.es6
+++ b/test/javascripts/acceptance/composer-test.js.es6
@@ -407,6 +407,44 @@ QUnit.test("Composer can toggle whispers", async assert => {
   );
 });
 
+QUnit.test("Switching composer whisper state", async assert => {
+  const menu = selectKit(".toolbar-popup-menu-options");
+
+  await visit("/t/this-is-a-test-topic/9");
+  await click(".topic-post:eq(0) button.reply");
+
+  await menu.expand();
+  await menu.selectRowByValue("toggleWhisper");
+
+  await fillIn(".d-editor-input", "this is the content of my reply");
+  await click("#reply-control button.create");
+
+  assert.ok(find(".topic-post:last").hasClass("whisper"));
+
+  await click("#topic-footer-buttons .btn.create");
+
+  assert.ok(
+    find(".composer-fields .whisper .d-icon-eye-slash").length === 0,
+    "doesn’t set topic reply as whisper"
+  );
+
+  await click(".topic-post:last button.reply");
+
+  assert.ok(find(".topic-post:last").hasClass("whisper"));
+  assert.ok(
+    find(".composer-fields .whisper .d-icon-eye-slash").length === 1,
+    "sets post reply as a whisper"
+  );
+
+  await click(".topic-post:nth-last-child(2) button.reply");
+
+  assert.notOk(find(".topic-post:nth-last-child(2)").hasClass("whisper"));
+  assert.ok(
+    find(".composer-fields .whisper .d-icon-eye-slash").length === 0,
+    "doesn’t set post reply as a whisper"
+  );
+});
+
 QUnit.test(
   "Composer can toggle layouts (open, fullscreen and draft)",
   async assert => {