diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module
index a465e47..4a0aca7 100644
--- a/core/modules/contextual/contextual.module
+++ b/core/modules/contextual/contextual.module
@@ -121,7 +121,13 @@ function contextual_library_info() {
     'title' => 'Contextual Links Toolbar Tab',
     'version' => \Drupal::VERSION,
     'js' => array(
+      // Core.
       $path . '/js/contextual.toolbar.js' => array('group' => JS_LIBRARY),
+      // Model.
+      $path . '/js/models/Model.js' => array('group' => JS_LIBRARY),
+      // Views.
+      $path . '/js/views/AuralView.js' => array('group' => JS_LIBRARY),
+      $path . '/js/views/VisualView.js' => array('group' => JS_LIBRARY),
     ),
     'css' => array(
       $path . '/css/contextual.toolbar.css' => array(),
diff --git a/core/modules/contextual/js/contextual.toolbar.js b/core/modules/contextual/js/contextual.toolbar.js
index 0c6a8b8..7f799ad 100644
--- a/core/modules/contextual/js/contextual.toolbar.js
+++ b/core/modules/contextual/js/contextual.toolbar.js
@@ -3,7 +3,7 @@
  * Attaches behaviors for the Contextual module's edit toolbar tab.
  */
 
-(function ($, Drupal, Backbone) {
+(function ($, Drupal) {
 
 "use strict";
 
@@ -59,234 +59,7 @@ Drupal.behaviors.contextualToolbar = {
  */
 Drupal.contextualToolbar = {
   // The Drupal.contextualToolbar.Model instance.
-  model: null,
-
-  /**
-   * Models the state of the edit mode toggle.
-   */
-  Model: Backbone.Model.extend({
-    defaults: {
-      // Indicates whether the toggle is currently in "view" or "edit" mode.
-      isViewing: true,
-      // Indicates whether the toggle should be visible or hidden. Automatically
-      // calculated, depends on contextualCount.
-      isVisible: false,
-      // Tracks how many contextual links exist on the page.
-      contextualCount: 0,
-      // A TabbingContext object as returned by Drupal.TabbingManager: the set
-      // of tabbable elements when edit mode is enabled.
-      tabbingContext: null
-    },
-
-    /**
-     * {@inheritdoc}
-     *
-     * @param Object attrs
-     * @param Object options
-     *   An object with the following option:
-     *     - Backbone.collection contextualCollection: the collection of
-     *       Drupal.contextual.Model models that represent the contextual links
-     *       on the page.
-     */
-    initialize: function (attrs, options) {
-      // Respond to new/removed contextual links.
-      this.listenTo(options.contextualCollection, {
-        'reset remove add': this.countCountextualLinks,
-        'add': this.lockNewContextualLinks
-      });
-
-      this.listenTo(this, {
-        // Automatically determine visibility.
-        'change:contextualCount': this.updateVisibility,
-        // Whenever edit mode is toggled, lock all contextual links.
-        'change:isViewing': function (model, isViewing) {
-          options.contextualCollection.each(function (contextualModel) {
-            contextualModel.set('isLocked', !isViewing);
-          });
-        }
-      });
-    },
-
-    /**
-     * Tracks the number of contextual link models in the collection.
-     *
-     * @param Drupal.contextual.Model affectedModel
-     *   The contextual links model that was added or removed.
-     * @param Backbone.Collection contextualCollection
-     *    The collection of contextual link models.
-     */
-    countCountextualLinks: function (contextualModel, contextualCollection) {
-      this.set('contextualCount', contextualCollection.length);
-    },
-
-    /**
-     * Lock newly added contextual links if edit mode is enabled.
-     *
-     * @param Drupal.contextual.Model addedContextualModel
-     *   The contextual links model that was added.
-     * @param Backbone.Collection contextualCollection
-     *    The collection of contextual link models.
-     */
-    lockNewContextualLinks: function (contextualModel, contextualCollection) {
-      if (!this.get('isViewing')) {
-        contextualModel.set('isLocked', true);
-      }
-    },
-
-    /**
-     * Automatically updates visibility of the view/edit mode toggle.
-     */
-    updateVisibility: function () {
-      this.set('isVisible', this.get('contextualCount') > 0);
-    }
-  }),
-
-  /**
-   * Renders the visual view of the edit mode toggle. Listens to mouse & touch.
-   *
-   * Handles edit mode toggle interactions.
-   */
-  VisualView: Backbone.View.extend({
-    events: function () {
-      // Prevents delay and simulated mouse events.
-      var touchEndToClick = function (event) {
-        event.preventDefault();
-        event.target.click();
-      };
-
-      return {
-        'click': function () {
-          this.model.set('isViewing', !this.model.get('isViewing'));
-        },
-        'touchend': touchEndToClick
-      };
-    },
-
-    /**
-     * {@inheritdoc}
-     */
-    initialize: function () {
-      this.listenTo(this.model, 'change', this.render);
-      this.listenTo(this.model, 'change:isViewing', this.persist);
-    },
-
-    /**
-     * {@inheritdoc}
-     */
-    render: function () {
-      // Render the visibility.
-      this.$el.toggleClass('hidden', !this.model.get('isVisible'));
-      // Render the state.
-      this.$el.find('button').toggleClass('active', !this.model.get('isViewing'));
-
-      return this;
-    },
-
-    /**
-     * Model change handler; persists the isViewing value to localStorage.
-     *
-     * isViewing === true is the default, so only stores in localStorage when
-     * it's not the default value (i.e. false).
-     *
-     * @param Drupal.contextualToolbar.Model model
-     *   A Drupal.contextualToolbar.Model model.
-     * @param bool isViewing
-     *   The value of the isViewing attribute in the model.
-     */
-    persist: function (model, isViewing) {
-      if (!isViewing) {
-        localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false');
-      }
-      else {
-        localStorage.removeItem('Drupal.contextualToolbar.isViewing');
-      }
-    }
-  }),
-
-  /**
-   * Renders the aural view of the edit mode toggle (i.e.screen reader support).
-   */
-  AuralView: Backbone.View.extend({
-    // Tracks whether the tabbing constraint announcement has been read once yet.
-    announcedOnce: false,
-
-    /*
-     * {@inheritdoc}
-     */
-    initialize: function (options) {
-      this.options = options;
-
-      this.listenTo(this.model, 'change', this.render);
-      this.listenTo(this.model, 'change:isViewing', this.manageTabbing);
-
-      $(document).on('keyup', _.bind(this.onKeypress, this));
-    },
-
-    /**
-     * {@inheritdoc}
-     */
-    render: function () {
-      // Render the state.
-      this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing'));
-
-      return this;
-    },
-
-    /**
-     * Limits tabbing to the contextual links and edit mode toolbar tab.
-     *
-     * @param Drupal.contextualToolbar.Model model
-     *   A Drupal.contextualToolbar.Model model.
-     * @param bool isViewing
-     *   The value of the isViewing attribute in the model.
-     */
-    manageTabbing: function () {
-      var tabbingContext = this.model.get('tabbingContext');
-      // Always release an existing tabbing context.
-      if (tabbingContext) {
-        tabbingContext.release();
-        Drupal.announce(this.options.strings.tabbingReleased);
-      }
-      // Create a new tabbing context when edit mode is enabled.
-      if (!this.model.get('isViewing')) {
-        tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual'));
-        this.model.set('tabbingContext', tabbingContext);
-        this.announceTabbingConstraint();
-        this.announcedOnce = true;
-      }
-    },
-
-    /**
-     * Announces the current tabbing constraint.
-     */
-    announceTabbingConstraint: function () {
-      var strings = this.options.strings;
-      Drupal.announce(Drupal.formatString(strings.tabbingConstrained, {
-        '@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links')
-      }));
-      Drupal.announce(strings.pressEsc);
-    },
-
-    /**
-     * Responds to esc and tab key press events.
-     *
-     * @param jQuery.Event event
-     */
-    onKeypress: function (event) {
-      // The first tab key press is tracked so that an annoucement about tabbing
-      // constraints can be raised if edit mode is enabled when the page is
-      // loaded.
-      if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) {
-        this.announceTabbingConstraint();
-        // Set announce to true so that this conditional block won't run again.
-        this.announcedOnce = true;
-      }
-      // Respond to the ESC key. Exit out of edit mode.
-      if (event.keyCode === 27) {
-        this.model.set('isViewing', true);
-      }
-    }
-  })
+  model: null
 };
 
-})(jQuery, Drupal, Backbone);
+})(jQuery, Drupal);
diff --git a/core/modules/contextual/js/models/Model.js b/core/modules/contextual/js/models/Model.js
new file mode 100644
index 0000000..a2c0501
--- /dev/null
+++ b/core/modules/contextual/js/models/Model.js
@@ -0,0 +1,90 @@
+/**
+ * @file
+ * Attaches behaviors for the Contextual module's edit toolbar tab.
+ */
+
+(function (Drupal, Backbone) {
+
+"use strict";
+
+/**
+ * Models the state of the edit mode toggle.
+ */
+Drupal.contextualToolbar.Model = Backbone.Model.extend({
+  defaults: {
+    // Indicates whether the toggle is currently in "view" or "edit" mode.
+    isViewing: true,
+    // Indicates whether the toggle should be visible or hidden. Automatically
+    // calculated, depends on contextualCount.
+    isVisible: false,
+    // Tracks how many contextual links exist on the page.
+    contextualCount: 0,
+    // A TabbingContext object as returned by Drupal.TabbingManager: the set
+    // of tabbable elements when edit mode is enabled.
+    tabbingContext: null
+  },
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param Object attrs
+   * @param Object options
+   *   An object with the following option:
+   *     - Backbone.collection contextualCollection: the collection of
+   *       Drupal.contextual.Model models that represent the contextual links
+   *       on the page.
+   */
+  initialize: function (attrs, options) {
+    // Respond to new/removed contextual links.
+    this.listenTo(options.contextualCollection, {
+      'reset remove add': this.countCountextualLinks,
+      'add': this.lockNewContextualLinks
+    });
+
+    this.listenTo(this, {
+      // Automatically determine visibility.
+      'change:contextualCount': this.updateVisibility,
+      // Whenever edit mode is toggled, lock all contextual links.
+      'change:isViewing': function (model, isViewing) {
+        options.contextualCollection.each(function (contextualModel) {
+          contextualModel.set('isLocked', !isViewing);
+        });
+      }
+    });
+  },
+
+  /**
+   * Tracks the number of contextual link models in the collection.
+   *
+   * @param Drupal.contextual.Model affectedModel
+   *   The contextual links model that was added or removed.
+   * @param Backbone.Collection contextualCollection
+   *    The collection of contextual link models.
+   */
+  countCountextualLinks: function (contextualModel, contextualCollection) {
+    this.set('contextualCount', contextualCollection.length);
+  },
+
+  /**
+   * Lock newly added contextual links if edit mode is enabled.
+   *
+   * @param Drupal.contextual.Model addedContextualModel
+   *   The contextual links model that was added.
+   * @param Backbone.Collection contextualCollection
+   *    The collection of contextual link models.
+   */
+  lockNewContextualLinks: function (contextualModel, contextualCollection) {
+    if (!this.get('isViewing')) {
+      contextualModel.set('isLocked', true);
+    }
+  },
+
+  /**
+   * Automatically updates visibility of the view/edit mode toggle.
+   */
+  updateVisibility: function () {
+    this.set('isVisible', this.get('contextualCount') > 0);
+  }
+});
+
+})(Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/AuralView.js b/core/modules/contextual/js/views/AuralView.js
new file mode 100644
index 0000000..3690c96
--- /dev/null
+++ b/core/modules/contextual/js/views/AuralView.js
@@ -0,0 +1,93 @@
+/**
+ * @file
+ * A Backbone View that renders the aural view of the edit mode toggle.
+ */
+
+(function ($, Drupal, Backbone) {
+
+"use strict";
+
+/**
+ * Renders the aural view of the edit mode toggle (i.e.screen reader support).
+ */
+Drupal.contextualToolbar.AuralView = Backbone.View.extend({
+  // Tracks whether the tabbing constraint announcement has been read once yet.
+  announcedOnce: false,
+
+  /*
+   * {@inheritdoc}
+   */
+  initialize: function () {
+    this.model.on('change', this.render, this);
+    this.model.on('change:isViewing', this.manageTabbing, this);
+
+    $(document).on('keyup', _.bind(this.onKeypress, this));
+  },
+
+  /**
+   * {@inheritdoc}
+   */
+  render: function () {
+    // Render the state.
+    this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing'));
+
+    return this;
+  },
+
+  /**
+   * Limits tabbing to the contextual links and edit mode toolbar tab.
+   *
+   * @param Drupal.contextualToolbar.Model model
+   *   A Drupal.contextualToolbar.Model model.
+   * @param bool isViewing
+   *   The value of the isViewing attribute in the model.
+   */
+  manageTabbing: function () {
+    var tabbingContext = this.model.get('tabbingContext');
+    // Always release an existing tabbing context.
+    if (tabbingContext) {
+      tabbingContext.release();
+      Drupal.announce(this.options.strings.tabbingReleased);
+    }
+    // Create a new tabbing context when edit mode is enabled.
+    if (!this.model.get('isViewing')) {
+      tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual'));
+      this.model.set('tabbingContext', tabbingContext);
+      this.announceTabbingConstraint();
+      this.announcedOnce = true;
+    }
+  },
+
+  /**
+   * Announces the current tabbing constraint.
+   */
+  announceTabbingConstraint: function () {
+    var strings = this.options.strings;
+    Drupal.announce(Drupal.formatString(strings.tabbingConstrained, {
+      '@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links')
+    }));
+    Drupal.announce(strings.pressEsc);
+  },
+
+  /**
+   * Responds to esc and tab key press events.
+   *
+   * @param jQuery.Event event
+   */
+  onKeypress: function (event) {
+    // The first tab key press is tracked so that an annoucement about tabbing
+    // constraints can be raised if edit mode is enabled when the page is
+    // loaded.
+    if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) {
+      this.announceTabbingConstraint();
+      // Set announce to true so that this conditional block won't run again.
+      this.announcedOnce = true;
+    }
+    // Respond to the ESC key. Exit out of edit mode.
+    if (event.keyCode === 27) {
+      this.model.set('isViewing', true);
+    }
+  }
+});
+
+})(jQuery, Drupal, Backbone);
diff --git a/core/modules/contextual/js/views/VisualView.js b/core/modules/contextual/js/views/VisualView.js
new file mode 100644
index 0000000..91a7c62
--- /dev/null
+++ b/core/modules/contextual/js/views/VisualView.js
@@ -0,0 +1,72 @@
+/**
+ * @file
+ * A Backbone View that renders the visual view of the edit mode toggle.
+ */
+
+(function (Drupal, Backbone) {
+
+"use strict";
+
+/**
+ * Renders the visual view of the edit mode toggle. Listens to mouse & touch.
+ *
+ * Handles edit mode toggle interactions.
+ */
+Drupal.contextualToolbar.VisualView = Backbone.View.extend({
+  events: function () {
+    // Prevents delay and simulated mouse events.
+    var touchEndToClick = function (event) {
+      event.preventDefault();
+      event.target.click();
+    };
+
+    return {
+      'click': function () {
+        this.model.set('isViewing', !this.model.get('isViewing'));
+      },
+      'touchend': touchEndToClick
+    };
+  },
+
+  /**
+   * {@inheritdoc}
+   */
+  initialize: function () {
+    this.model.on('change', this.render, this);
+    this.model.on('change:isViewing', this.persist, this);
+  },
+
+  /**
+   * {@inheritdoc}
+   */
+  render: function () {
+    // Render the visibility.
+    this.$el.toggleClass('hidden', !this.model.get('isVisible'));
+    // Render the state.
+    this.$el.find('button').toggleClass('active', !this.model.get('isViewing'));
+
+    return this;
+  },
+
+  /**
+   * Model change handler; persists the isViewing value to localStorage.
+   *
+   * isViewing === true is the default, so only stores in localStorage when
+   * it's not the default value (i.e. false).
+   *
+   * @param Drupal.contextualToolbar.Model model
+   *   A Drupal.contextualToolbar.Model model.
+   * @param bool isViewing
+   *   The value of the isViewing attribute in the model.
+   */
+  persist: function (model, isViewing) {
+    if (!isViewing) {
+      localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false');
+    }
+    else {
+      localStorage.removeItem('Drupal.contextualToolbar.isViewing');
+    }
+  }
+});
+
+})(Drupal, Backbone);
