diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d94389ae83fcb6f577e91f09e5b4c9c09f9cf4d..c0fed8c2bda41f087ed55b6505a877b2dd7fc17f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ This file contains the changes applied to the sources of converse<br>
 The current build version of converse is: `v7.0.3dev`<br>
 If the converse sources have to be updated, apply these changes again, or fix them using plugins
 
+### 2021-05-19
+* Avoid clearing the textarea when pressing the ESC key and not editing any messages
+
 ### 2021-05-18
 * Move upload to fetch method from solid auth (commit 5345f05d9a4c71720e2c87a0a19c56a7b4865329)
 
diff --git a/src/conversejs/converse.js b/src/conversejs/converse.js
index 8cdce6057ac643fe3e9f8c5d0186b2aeb1d5e3e6..e86a471bf1938ebfae7fbd0c0e79f54dade00400 100644
--- a/src/conversejs/converse.js
+++ b/src/conversejs/converse.js
@@ -98225,9 +98225,8 @@ const ChatBoxView = View.extend({
 
     if (message) {
       message.save('correcting', false);
+      this.insertIntoTextArea('', true, false);
     }
-
-    this.insertIntoTextArea('', true, false);
   },
 
   async onMessageRetractButtonClicked(message) {
diff --git a/src/plugins/sib-emoji-picker.js b/src/plugins/sib-emoji-picker.js
index f4448c0fd3b1604a02e33fa451f361e022c3563c..aa462a81a7f9bcc4af19bff239f96287e65efed7 100644
--- a/src/plugins/sib-emoji-picker.js
+++ b/src/plugins/sib-emoji-picker.js
@@ -1,4 +1,5 @@
 import { Picker } from 'https://cdn.skypack.dev/emoji-picker-element';
+import { isTouchDevice } from '../utils.js';
 
 /**
  * Custom emoji picker.
@@ -54,7 +55,15 @@ converse.plugins.add('sib-emoji-picker', {
 
         picker = new Picker();
         picker.classList.add('light');
-        picker.addEventListener('emoji-click', ev => this.insertIntoTextArea(ev.detail.unicode));
+
+        picker.addEventListener('emoji-click', ev => {
+          if (isTouchDevice()) {
+            // Close the dropdown once we choose one on mobile devices
+            this.hideMenu();
+          }
+          this.insertIntoTextArea(ev.detail.unicode);
+        });
+
         this.menu.appendChild(picker);
 
         await _showMenu.apply(this, arguments);
diff --git a/src/plugins/sib-reactions.js b/src/plugins/sib-reactions.js
index 19a8aeb3f263566263820a8065ae9089641fef47..61d54da65c249b7dc0cf1f206782e8dbfb903a23 100644
--- a/src/plugins/sib-reactions.js
+++ b/src/plugins/sib-reactions.js
@@ -273,34 +273,52 @@ converse.plugins.add('sib-reactions', {
 
         picker.style.position = 'fixed';
         picker.style.zIndex = '99999';
-        picker.style.margin = '10px';
-        picker.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
-        picker.style.borderRadius = '3px';
 
         let pr = picker.getBoundingClientRect();
 
-        // Stick on the left side of the target
-        // Stick on the right side if there is not enough space for it
-        // Center if there are no space anywhere
-        if (window.innerWidth - (rectLeft + tr.width) > pr.width) {
-          picker.style.left = `${rectLeft + tr.width}px`;
-        } else if (rectLeft > pr.width) {
-          picker.style.right = `${window.innerWidth - rectLeft}px`;
-        } else {
-          picker.style.left = '50%';
-          picker.style.transform = 'translateX(-50%)';
-          picker.style.margin = '0px';
-        }
+        // Check if the viewport is mobile friendly
+        // We have to do this here since the styles is scoped to the SXC component shadow DOM
+        const mql = window.matchMedia('(max-width: 767.98px)');
 
-        // Add an offset so it's a bit higher than the target
-        const offY = 100;
+        if (mql.matches) {
 
-        // Show the drawer a bit higher than the target
-        // Stick at the bottom if there are no space available
-        if (pr.height + (rectTop - offY) > window.innerHeight) {
-          picker.style.bottom = `0px`;
+          // MOBILE
+          picker.style.left = '0';
+          picker.style.bottom = '0';
+          picker.style.width = '100%';
+          picker.style.height = '60vh';
+          picker.style.boxShadow = '0 0 0 100vh rgba(0, 0, 0, 0.3)';
+          picker.style.setProperty('--emoji-size', '1.6rem');
         } else {
-          picker.style.top = `${rectTop - offY}px`;
+
+          // DESKTOP
+          // Stick on the left side of the target
+          // Stick on the right side if there is not enough space for it
+          // Center if there are no space anywhere
+          picker.style.margin = '10px';
+          picker.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
+          picker.style.borderRadius = '3px';
+
+          if (window.innerWidth - (rectLeft + tr.width) > pr.width) {
+            picker.style.left = `${rectLeft + tr.width}px`;
+          } else if (rectLeft > pr.width) {
+            picker.style.right = `${window.innerWidth - rectLeft}px`;
+          } else {
+            picker.style.left = '50%';
+            picker.style.transform = 'translateX(-50%)';
+            picker.style.margin = '0px';
+          }
+
+          // Add an offset so it's a bit higher than the target
+          const offY = 100;
+
+          // Show the drawer a bit higher than the target
+          // Stick at the bottom if there are no space available
+          if (pr.height + (rectTop - offY) > window.innerHeight) {
+            picker.style.bottom = `0px`;
+          } else {
+            picker.style.top = `${rectTop - offY}px`;
+          }
         }
 
         // Focus the search input when the picker opens
diff --git a/src/plugins/sib-scroll-down-on-focus.js b/src/plugins/sib-scroll-down-on-focus.js
index 11652ab4283e4a5b4d9d1723b1ed2e33b7cda9b3..196c8f6a89810172f6e8e96ebaa56b6d7fc56291 100644
--- a/src/plugins/sib-scroll-down-on-focus.js
+++ b/src/plugins/sib-scroll-down-on-focus.js
@@ -1,17 +1,68 @@
 /**
- * Forces the active chat to scroll down when the tab is focused again.
+ * Forces the active chat to show the new messages indicator
+ * when the tab is focused again and we where previously scrolled down.
+ * TODO: Rename this plugin
  */
 converse.plugins.add('sib-scroll-down-on-focus', {
+  overrides: {
+    ChatBoxView: {
+      /**
+       * Force the chat view to be set as active when visible.
+       * Like it's set on "inactive" when the tab is focused out.
+       * @param {string} state
+       */
+      onWindowStateChanged(state) {
+        const { _converse } = this.__super__;
+
+        if (state === 'visible' && !this.model.isHidden()) {
+          this.model.setChatState(_converse.ACTIVE, {
+            'silent': true,
+          });
+        }
+
+        this.__super__.onWindowStateChanged.apply(this, arguments);
+      },
+
+      /**
+       * @inheritDoc
+       */
+      onMessageAdded(message) {
+        const { _converse } = this.__super__;
+
+        // On new message, set the chat view as scrolled
+        // so the new messages indicator is properly displayed
+        // when the view is inactive (tab not focused).
+        if (this.model.get('chat_state') === _converse.INACTIVE) {
+          this.model.set('scrolled', true);
+        }
+
+        this.__super__.onMessageAdded.apply(this, arguments);
+      },
+    },
+  },
   initialize() {
     const _converse = this._converse;
     const { api } = _converse;
 
-    api.listen.on('windowStateChanged', async data => {
-      if (data.state === 'visible' && api.connection.connected()) {
-        const chatBox = _converse.chatboxes.findWhere({ hidden: false });
-        const chatView = _converse.chatboxviews.get(chatBox?.get('jid'));
-        chatView?.scrollDown();
+    // Return the current root component
+    function getRootComponent() {
+      return api.settings.get('root')?.getRootNode()?.host?.component;
+    }
+
+    // When the chat view has been scrolled to the bottom
+    // send the read event to remove SIB notifications in UI
+    api.listen.on('chatBoxScrolledDown', (data) => {
+      if (data.chatbox.isHidden()) {
+        return;
       }
+
+      window.dispatchEvent(new CustomEvent('read', {
+        detail: {
+          resource: {
+            '@id': getRootComponent()?.resource['@id'],
+          },
+        },
+      }));
     });
   },
 });
diff --git a/src/plugins/sib-send-button-mobile.js b/src/plugins/sib-send-button-mobile.js
index fc3ef85adcaf20a14687898d832aa04e6c09cff0..19ea6fcc98aa3474b5831f61762afbbbbbb7c6e1 100644
--- a/src/plugins/sib-send-button-mobile.js
+++ b/src/plugins/sib-send-button-mobile.js
@@ -1,3 +1,5 @@
+import { isTouchDevice } from '../utils.js';
+
 /**
  * Prevent the "Enter" button to send the message
  * on touch devices.
@@ -9,9 +11,7 @@ converse.plugins.add('sib-send-button-mobile', {
        * @param {InputEvent} ev
        */
       onEnterPressed(ev) {
-        const touchDevice = ('ontouchstart' in document.documentElement);
-
-        if (touchDevice) {
+        if (isTouchDevice()) {
           // The line break is automatically inserted
           return;
         }
diff --git a/src/styles/index.scss b/src/styles/index.scss
index ade77204a88b6a2066854b602fdebca802cdff69..0c33520c252d422a1327946b0681987e277c33ed 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -1002,4 +1002,21 @@
     background: none;
     color: var(--color-secondary) !important;
   }
+
+  //
+  //  Emoji picker (mobile)
+  // ---------------------------------
+
+  @media (max-width: 767.98px) {
+    .dropdown-menu emoji-picker {
+      z-index: 99999;
+      position: fixed !important;
+      bottom: 0 !important;
+      left: 0 !important;
+      width: 100%;
+      height: 60vh;
+      --emoji-size: 1.6rem;
+      box-shadow: 0 0 0 100vh rgba(0, 0, 0, 0.3);
+    }
+  }
 }
diff --git a/src/utils.js b/src/utils.js
index 01f067ac60462f6eef371eb4607c31f374113670..a66a5de9833ccc4d47c0c7b9a47510dbb9f90eb2 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -8,3 +8,9 @@ export class Deferred {
     this.catch = this.promise.catch.bind(this.promise);
   }
 }
+
+/**
+ * Returns TRUE if the current device has touch features (mobile).
+ * @return {boolean}
+ */
+export const isTouchDevice = () => ('ontouchstart' in document.documentElement);