diff --git a/src/Keyboard.ts b/src/Keyboard.ts
index f5cf0a5492..817d0a0b97 100644
--- a/src/Keyboard.ts
+++ b/src/Keyboard.ts
@@ -36,6 +36,7 @@ export const Key = {
     CONTEXT_MENU: "ContextMenu",
 
     COMMA: ",",
+    PERIOD: ".",
     LESS_THAN: "<",
     GREATER_THAN: ">",
     BACKTICK: "`",
diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx
index a2fc12e5f5..c2739beefa 100644
--- a/src/accessibility/KeyboardShortcuts.tsx
+++ b/src/accessibility/KeyboardShortcuts.tsx
@@ -218,6 +218,12 @@ const shortcuts: Record<Categories, IShortcut[]> = {
                 key: Key.SPACE,
             }],
             description: _td("Activate selected button"),
+        }, {
+            keybinds: [{
+                modifiers: [CMD_OR_CTRL],
+                key: Key.PERIOD,
+            }],
+            description: _td("Toggle right panel"),
         }, {
             keybinds: [{
                 modifiers: [CMD_OR_CTRL],
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 0d10dd6d8f..524694fe95 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -424,6 +424,7 @@ export default createReactClass({
             membershipBusy: false,
             publicityBusy: false,
             inviterProfile: null,
+            showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup,
         };
     },
 
@@ -436,12 +437,18 @@ export default createReactClass({
         this._initGroupStore(this.props.groupId, true);
 
         this._dispatcherRef = dis.register(this._onAction);
+        this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate);
     },
 
     componentWillUnmount: function() {
         this._unmounted = true;
         this._matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
         dis.unregister(this._dispatcherRef);
+
+        // Remove RightPanelStore listener
+        if (this._rightPanelStoreToken) {
+            this._rightPanelStoreToken.remove();
+        }
     },
 
     componentWillReceiveProps: function(newProps) {
@@ -455,6 +462,12 @@ export default createReactClass({
         }
     },
 
+    _onRightPanelStoreUpdate: function() {
+        this.setState({
+            showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup,
+        });
+    },
+
     _onGroupMyMembership: function(group) {
         if (this._unmounted || group.groupId !== this.props.groupId) return;
         if (group.myMembership === 'leave') {
@@ -577,10 +590,6 @@ export default createReactClass({
                     profileForm: null,
                 });
                 break;
-            case 'after_right_panel_phase_change':
-                // We don't keep state on the right panel, so just re-render to update
-                this.forceUpdate();
-                break;
             default:
                 break;
         }
@@ -1295,9 +1304,7 @@ export default createReactClass({
                 );
             }
 
-            const rightPanel = RightPanelStore.getSharedInstance().isOpenForGroup
-                ? <RightPanel groupId={this.props.groupId} />
-                : undefined;
+            const rightPanel = this.state.showRightPanel ? <RightPanel groupId={this.props.groupId} /> : undefined;
 
             const headerClasses = {
                 "mx_GroupView_header": true,
diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js
index 41fbd54991..e7a6f4c1a9 100644
--- a/src/components/structures/LoggedInView.js
+++ b/src/components/structures/LoggedInView.js
@@ -397,6 +397,15 @@ const LoggedInView = createReactClass({
                     handled = true;
                 }
                 break;
+
+            case Key.PERIOD:
+                if (ctrlCmdOnly && (this.props.page_type === "room_view" || this.props.page_type === "group_view")) {
+                    dis.dispatch({
+                        action: 'toggle_right_panel',
+                        type: this.props.page_type === "room_view" ? "room" : "group",
+                    });
+                    handled = true;
+                }
         }
 
         if (handled) {
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 78f32cf63e..0bdbf44bcd 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -132,6 +132,7 @@ export default createReactClass({
             isPeeking: false,
             showingPinned: false,
             showReadReceipts: true,
+            showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
 
             // error object, as from the matrix client/server API
             // If we failed to load information about the room,
@@ -177,6 +178,7 @@ export default createReactClass({
         MatrixClientPeg.get().on("userTrustStatusChanged", this.onUserVerificationChanged);
         // Start listening for RoomViewStore updates
         this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
+        this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate);
         this._onRoomViewStoreUpdate(true);
 
         WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate);
@@ -500,6 +502,10 @@ export default createReactClass({
         if (this._roomStoreToken) {
             this._roomStoreToken.remove();
         }
+        // Remove RightPanelStore listener
+        if (this._rightPanelStoreToken) {
+            this._rightPanelStoreToken.remove();
+        }
 
         WidgetEchoStore.removeListener('update', this._onWidgetEchoStoreUpdate);
 
@@ -516,6 +522,12 @@ export default createReactClass({
         // Tinter.tint(); // reset colourscheme
     },
 
+    _onRightPanelStoreUpdate: function() {
+        this.setState({
+            showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
+        });
+    },
+
     onPageUnload(event) {
         if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
             return event.returnValue =
@@ -555,10 +567,6 @@ export default createReactClass({
 
     onAction: function(payload) {
         switch (payload.action) {
-            case 'after_right_panel_phase_change':
-                // We don't keep state on the right panel, so just re-render to update
-                this.forceUpdate();
-                break;
             case 'message_send_failed':
             case 'message_sent':
                 this._checkIfAlone(this.state.room);
@@ -2014,8 +2022,7 @@ export default createReactClass({
             },
         );
 
-        const showRightPanel = !forceHideRightPanel && this.state.room
-            && RightPanelStore.getSharedInstance().isOpenForRoom;
+        const showRightPanel = !forceHideRightPanel && this.state.room && this.state.showRightPanel;
         const rightPanel = showRightPanel
             ? <RightPanel roomId={this.state.room.roomId} resizeNotifier={this.props.resizeNotifier} />
             : null;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 33f8545bda..22a6a2f222 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2213,6 +2213,7 @@
     "Toggle the top left menu": "Toggle the top left menu",
     "Close dialog or context menu": "Close dialog or context menu",
     "Activate selected button": "Activate selected button",
+    "Toggle right panel": "Toggle right panel",
     "Toggle this dialog": "Toggle this dialog",
     "Move autocomplete selection up/down": "Move autocomplete selection up/down",
     "Cancel autocomplete": "Cancel autocomplete",
diff --git a/src/stores/RightPanelStore.js b/src/stores/RightPanelStore.js
index ccdeb006f4..814f54b454 100644
--- a/src/stores/RightPanelStore.js
+++ b/src/stores/RightPanelStore.js
@@ -35,6 +35,12 @@ const INITIAL_STATE = {
 
 const GROUP_PHASES = Object.keys(RIGHT_PANEL_PHASES).filter(k => k.startsWith("Group"));
 
+const MEMBER_INFO_PHASES = [
+    RIGHT_PANEL_PHASES.RoomMemberInfo,
+    RIGHT_PANEL_PHASES.Room3pidMemberInfo,
+    RIGHT_PANEL_PHASES.EncryptionPanel,
+];
+
 /**
  * A class for tracking the state of the right panel between layouts and
  * sessions.
@@ -114,62 +120,69 @@ export default class RightPanelStore extends Store {
     }
 
     __onDispatch(payload) {
-        if (payload.action === 'view_room' || payload.action === 'view_group') {
-            // Reset to the member list if we're viewing member info
-            const memberInfoPhases = [
-                RIGHT_PANEL_PHASES.RoomMemberInfo,
-                RIGHT_PANEL_PHASES.Room3pidMemberInfo,
-                RIGHT_PANEL_PHASES.EncryptionPanel,
-            ];
-            if (memberInfoPhases.includes(this._state.lastRoomPhase)) {
-                this._setState({lastRoomPhase: RIGHT_PANEL_PHASES.RoomMemberList, lastRoomPhaseParams: {}});
+        switch (payload.action) {
+            case 'view_room':
+            case 'view_group':
+                // Reset to the member list if we're viewing member info
+                if (MEMBER_INFO_PHASES.includes(this._state.lastRoomPhase)) {
+                    this._setState({lastRoomPhase: RIGHT_PANEL_PHASES.RoomMemberList, lastRoomPhaseParams: {}});
+                }
+
+                // Do the same for groups
+                if (this._state.lastGroupPhase === RIGHT_PANEL_PHASES.GroupMemberInfo) {
+                    this._setState({lastGroupPhase: RIGHT_PANEL_PHASES.GroupMemberList});
+                }
+                break;
+
+            case 'set_right_panel_phase': {
+                const targetPhase = payload.phase;
+                if (!RIGHT_PANEL_PHASES[targetPhase]) {
+                    console.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`);
+                    return;
+                }
+
+                if (GROUP_PHASES.includes(targetPhase)) {
+                    if (targetPhase === this._state.lastGroupPhase) {
+                        this._setState({
+                            showGroupPanel: !this._state.showGroupPanel,
+                        });
+                    } else {
+                        this._setState({
+                            lastGroupPhase: targetPhase,
+                            showGroupPanel: true,
+                        });
+                    }
+                } else {
+                    if (targetPhase === this._state.lastRoomPhase && !payload.refireParams) {
+                        this._setState({
+                            showRoomPanel: !this._state.showRoomPanel,
+                        });
+                    } else {
+                        this._setState({
+                            lastRoomPhase: targetPhase,
+                            showRoomPanel: true,
+                            lastRoomPhaseParams: payload.refireParams || {},
+                        });
+                    }
+                }
+
+                // Let things like the member info panel actually open to the right member.
+                dis.dispatch({
+                    action: 'after_right_panel_phase_change',
+                    phase: targetPhase,
+                    ...(payload.refireParams || {}),
+                });
+                break;
             }
 
-            // Do the same for groups
-            if (this._state.lastGroupPhase === RIGHT_PANEL_PHASES.GroupMemberInfo) {
-                this._setState({lastGroupPhase: RIGHT_PANEL_PHASES.GroupMemberList});
-            }
+            case 'toggle_right_panel':
+                if (payload.type === "room") {
+                    this._setState({ showRoomPanel: !this._state.showRoomPanel });
+                } else { // group
+                    this._setState({ showGroupPanel: !this._state.showGroupPanel });
+                }
+                break;
         }
-
-        if (payload.action !== 'set_right_panel_phase') return;
-
-        const targetPhase = payload.phase;
-        if (!RIGHT_PANEL_PHASES[targetPhase]) {
-            console.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`);
-            return;
-        }
-
-        if (GROUP_PHASES.includes(targetPhase)) {
-            if (targetPhase === this._state.lastGroupPhase) {
-                this._setState({
-                    showGroupPanel: !this._state.showGroupPanel,
-                });
-            } else {
-                this._setState({
-                    lastGroupPhase: targetPhase,
-                    showGroupPanel: true,
-                });
-            }
-        } else {
-            if (targetPhase === this._state.lastRoomPhase && !payload.refireParams) {
-                this._setState({
-                    showRoomPanel: !this._state.showRoomPanel,
-                });
-            } else {
-                this._setState({
-                    lastRoomPhase: targetPhase,
-                    showRoomPanel: true,
-                    lastRoomPhaseParams: payload.refireParams || {},
-                });
-            }
-        }
-
-        // Let things like the member info panel actually open to the right member.
-        dis.dispatch({
-            action: 'after_right_panel_phase_change',
-            phase: targetPhase,
-            ...(payload.refireParams || {}),
-        });
     }
 
     static getSharedInstance(): RightPanelStore {