diff --git a/src/plugins/substep/README.md b/src/plugins/substep/README.md
new file mode 100644
index 0000000..e4277ff
--- /dev/null
+++ b/src/plugins/substep/README.md
@@ -0,0 +1,37 @@
+Substep Plugin
+===============
+
+Reveal each substep (such as a bullet point) of the step separately. Just like in PowerPoint!
+
+If the current step contains html elements with `class="substep"` then this plugin will prevent a
+`prev()` / `next()` call to leave the slide, and instead reveal the next substep (for `next()`) or
+alternatively hide one (for `prev()`). Only once all substeps are shown, will a call to `next()`
+actually move to the next step, and only when all are hidden will a call to `prev()` move to the
+previous one.
+
+Calls to `goto()` will be ignored by this plugin, i.e. `goto()` will transition to whichever step is
+the target.
+
+In practice what happens is that when each substep is stepped through via `next()` calls, a
+`class="substep-visible"` class is added to the element. It is up to the presentation author to
+use the appropriate CSS to make the substeps hidden and visible.
+
+Example:
+
+
+
+
+
Fruits
+
Orange
+
Apple
+
+
+Author
+------
+
+Copyright 2017 Henrik Ingo (@henrikingo)
+Released under the MIT license.
+
diff --git a/src/plugins/substep/substep.js b/src/plugins/substep/substep.js
new file mode 100644
index 0000000..d5a6465
--- /dev/null
+++ b/src/plugins/substep/substep.js
@@ -0,0 +1,111 @@
+/**
+ * Substep Plugin
+ *
+ * Copyright 2017 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+
+/* global document, window */
+
+( function( document, window ) {
+ "use strict";
+
+ // Copied from core impress.js. Good candidate for moving to src/lib/util.js.
+ var triggerEvent = function( el, eventName, detail ) {
+ var event = document.createEvent( "CustomEvent" );
+ event.initCustomEvent( eventName, true, true, detail );
+ el.dispatchEvent( event );
+ };
+
+ var activeStep = null;
+ document.addEventListener( "impress:stepenter", function( event ) {
+ activeStep = event.target;
+ }, false );
+
+ var substep = function( event ) {
+ if ( ( !event ) || ( !event.target ) ) {
+ return;
+ }
+
+ var step = event.target;
+ var el; // Needed by jshint
+ if ( event.detail.reason === "next" ) {
+ el = showSubstepIfAny( step );
+ if ( el ) {
+
+ // Send a message to others, that we aborted a stepleave event.
+ // Autoplay will reload itself from this, as there won't be a stepenter event now.
+ triggerEvent( step, "impress:substep:stepleaveaborted",
+ { reason: "next", substep: el } );
+
+ // Returning false aborts the stepleave event
+ return false;
+ }
+ }
+ if ( event.detail.reason === "prev" ) {
+ el = hideSubstepIfAny( step );
+ if ( el ) {
+ triggerEvent( step, "impress:substep:stepleaveaborted",
+ { reason: "prev", substep: el } );
+ return false;
+ }
+ }
+ };
+
+ var showSubstepIfAny = function( step ) {
+ var substeps = step.querySelectorAll( ".substep" );
+ var visible = step.querySelectorAll( ".substep-visible" );
+ if ( substeps.length > 0 ) {
+ return showSubstep( substeps, visible );
+ }
+ };
+
+ var showSubstep = function( substeps, visible ) {
+ if ( visible.length < substeps.length ) {
+ var el = substeps[ visible.length ];
+ el.classList.add( "substep-visible" );
+ return el;
+ }
+ };
+
+ var hideSubstepIfAny = function( step ) {
+ var substeps = step.querySelectorAll( ".substep" );
+ var visible = step.querySelectorAll( ".substep-visible" );
+ if ( substeps.length > 0 ) {
+ return hideSubstep( visible );
+ }
+ };
+
+ var hideSubstep = function( visible ) {
+ if ( visible.length > 0 ) {
+ var el = visible[ visible.length - 1 ];
+ el.classList.remove( "substep-visible" );
+ return el;
+ }
+ };
+
+ // Register the plugin to be called in pre-stepleave phase.
+ // The weight makes this plugin run before other preStepLeave plugins.
+ window.impress.addPreStepLeavePlugin( substep, 1 );
+
+ // When entering a step, in particular when re-entering, make sure that all substeps are hidden
+ // at first
+ document.addEventListener( "impress:stepenter", function( event ) {
+ var step = event.target;
+ var visible = step.querySelectorAll( ".substep-visible" );
+ for ( var i = 0; i < visible.length; i++ ) {
+ visible[ i ].classList.remove( "substep-visible" );
+ }
+ }, false );
+
+ // API for others to reveal/hide next substep ////////////////////////////////////////////////
+ document.addEventListener( "impress:substep:show", function() {
+ showSubstepIfAny( activeStep );
+ }, false );
+
+ document.addEventListener( "impress:substep:hide", function() {
+ hideSubstepIfAny( activeStep );
+ }, false );
+
+} )( document, window );
+