diff --git a/js/reveal.js b/js/reveal.js
index 7a032d0c59c30f6aa1ed002385925e73e8f1cc39..d81a19d16010bdf304e2f5f6a20f3a0e4c279054 100644
--- a/js/reveal.js
+++ b/js/reveal.js
@@ -3702,6 +3702,11 @@ var Reveal = (function(){
 			if( 'addEventListener' in window ) {
 				( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
 			}
+		},
+
+		// Programatically triggers a keyboard event
+		triggerKey: function( keyCode ) {
+			onDocumentKeyDown( { keyCode: keyCode } );
 		}
 	};
 
diff --git a/plugin/notes/notes.html b/plugin/notes/notes.html
index e4880750ac10a8ba21736817b70adb1eac0ebc86..30e1669fcc2f758bd6caf035c0a144ce89ca80ac 100644
--- a/plugin/notes/notes.html
+++ b/plugin/notes/notes.html
@@ -208,6 +208,7 @@
 						connected = true;
 
 						setupIframes( data );
+						setupKeyboard();
 						setupNotes();
 						setupTimer();
 					}
@@ -247,6 +248,19 @@
 				// Limit to max one state update per X ms
 				handleStateMessage = debounce( handleStateMessage, 200 );
 
+				/**
+				 * Forward keyboard events to the current slide window.
+				 * This enables keyboard events to work even if focus
+				 * isn't set on the current slide iframe.
+				 */
+				function setupKeyboard() {
+
+					document.addEventListener( 'keydown', function( event ) {
+						currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
+					} );
+
+				}
+
 				/**
 				 * Creates the preview iframes.
 				 */