diff --git a/core/misc/tabbingmanager.js b/core/misc/tabbingmanager.js
index 4593ec4..11cee64 100644
--- a/core/misc/tabbingmanager.js
+++ b/core/misc/tabbingmanager.js
@@ -69,7 +69,10 @@
      *
      * @fires event:drupalTabbingConstrained
      */
-    constrain: function (elements) {
+    constrain: function (elements, comprehensive) {
+      if (typeof comprehensive === 'undefined') {
+        comprehensive = false;
+      }
       // Deactivate all tabbingContexts to prepare for the new constraint. A
       // tabbingContext instance will only be reactivated if the stack is
       // unwound to it in the _unwindStack() method.
@@ -80,7 +83,16 @@
 
       // The "active tabbing set" are the elements tabbing should be constrained
       // to.
-      var $elements = $(elements).find(':tabbable').addBack(':tabbable');
+      var $elements;
+      // Find tabbable elements within the set of supplied elements if expansion
+      // is requested.
+      if (!comprehensive) {
+        $elements = $(elements).find(':tabbable').addBack(':tabbable');
+      }
+      // Assume that a list of tabbable elements is provided.
+      else {
+        $elements = $(elements);
+      }
 
       var tabbingContext = new TabbingContext({
         // The level is the current height of the stack before this new
diff --git a/core/modules/quickedit/css/quickedit.theme.css b/core/modules/quickedit/css/quickedit.theme.css
index 399db7a..388fca1 100644
--- a/core/modules/quickedit/css/quickedit.theme.css
+++ b/core/modules/quickedit/css/quickedit.theme.css
@@ -10,6 +10,10 @@
 .quickedit-field .quickedit-editable {
   box-shadow: 0 0 0 2px #74b7ff;
 }
+.quickedit-field.quickedit-editable:focus,
+.quickedit-field .quickedit-editable:focus {
+  box-shadow: 0 0 0 4px #74b7ff;
+}
 
 /**
  * Highlighted (hovered) editable.
diff --git a/core/modules/quickedit/js/editors/formEditor.js b/core/modules/quickedit/js/editors/formEditor.js
index c612a28..5694f3b 100644
--- a/core/modules/quickedit/js/editors/formEditor.js
+++ b/core/modules/quickedit/js/editors/formEditor.js
@@ -100,12 +100,21 @@
       // Render form container.
       var $formContainer = this.$formContainer = $(Drupal.theme('quickeditFormContainer', {
         id: id,
+        'aria-label': Drupal.t('Edit form for @aria', {'@aria': fieldModel.get('metadata').aria}),
         loadingMsg: Drupal.t('Loading…')
       }));
+      // Add this form's id to the list of elements that the entity owns.
+      var $entity = $(this.fieldModel.get('entity').get('el'));
+      var owns = $entity.attr('aria-owns');
+      if (owns) {
+        var ownsValues = owns.split(/\s+/);
+        ownsValues.push(id);
+        $entity.attr('aria-owns', ownsValues.join(' '));
+      }
+      // Mark up the form with state classes.
       $formContainer
         .find('.quickedit-form')
-        .addClass('quickedit-editable quickedit-highlighted quickedit-editing')
-        .attr('role', 'dialog');
+        .addClass('quickedit-editable quickedit-highlighted quickedit-editing');
 
       // Insert form container in DOM.
       if (this.$el.css('display') === 'inline') {
@@ -159,6 +168,9 @@
             }
           });
 
+        // Set focus on the form, so that the next tab goes to the first field
+        // in this container.
+        $formContainer.trigger('focus');
         // The in-place editor has loaded; change state to 'active'.
         fieldModel.set('state', 'active');
       });
@@ -180,6 +192,18 @@
         .off('keypress.quickedit', 'input')
         .remove();
       this.$formContainer = null;
+      // Remove the form id from the entity's aria-owns attribute.
+      var $entity = $(this.fieldModel.get('entity').get('el'));
+      var owns = $entity.attr('aria-owns');
+      if (owns) {
+        var ownsValues = owns
+          .split(/\s+/)
+          .filter(function (id) {
+            // Remove id values that start with 'quickedit-form-for-'.
+            return (id.indexOf('quickedit-form-for-') !== 0);
+          });
+        $entity.attr('aria-owns', ownsValues.join(' '));
+      }
     },
 
     /**
diff --git a/core/modules/quickedit/js/theme.js b/core/modules/quickedit/js/theme.js
index 8ea7a31..c814510 100644
--- a/core/modules/quickedit/js/theme.js
+++ b/core/modules/quickedit/js/theme.js
@@ -37,7 +37,7 @@
    */
   Drupal.theme.quickeditEntityToolbar = function (settings) {
     var html = '';
-    html += '<div id="' + settings.id + '" class="quickedit quickedit-toolbar-container clearfix">';
+    html += '<div id="' + settings.id + '" class="quickedit quickedit-toolbar-container clearfix" role="dialog">';
     html += '<i class="quickedit-toolbar-pointer"></i>';
     html += '<div class="quickedit-toolbar-content">';
     html += '<div class="quickedit-toolbar quickedit-toolbar-entity clearfix icon icon-pencil">';
@@ -174,7 +174,7 @@
    */
   Drupal.theme.quickeditFormContainer = function (settings) {
     var html = '';
-    html += '<div id="' + settings.id + '" class="quickedit-form-container">';
+    html += '<div id="' + settings.id + '" class="quickedit-form-container" role="dialog" tabindex="0" aria-label="' + settings['aria-label'] + '">';
     html += '  <div class="quickedit-form">';
     html += '    <div class="placeholder">';
     html += settings.loadingMsg;
diff --git a/core/modules/quickedit/js/views/AppView.js b/core/modules/quickedit/js/views/AppView.js
index dbffdd6..398ec29 100644
--- a/core/modules/quickedit/js/views/AppView.js
+++ b/core/modules/quickedit/js/views/AppView.js
@@ -91,6 +91,13 @@
           });
           break;
 
+        case 'opened':
+          // Constrain the tabbing context.
+          if (!app.tabbingContext) {
+            app.tabbingContext = Drupal.tabbingManager.constrain('.quickedit-editable, #quickedit-entity-toolbar .quickedit-toolbar button', false);
+          }
+          break;
+
         case 'closed':
           entityToolbarView = entityModel.toolbarView;
           // First, tear down the in-place editors.
@@ -102,6 +109,11 @@
             entityToolbarView.remove();
             delete entityModel.toolbarView;
           }
+          // Release the tabbing context.
+          if (app.tabbingContext) {
+            app.tabbingContext.release();
+            app.tabbingContext = null;
+          }
           // A page reload may be necessary to re-instate the original HTML of
           // the edited fields.
           if (reload) {
@@ -270,6 +282,12 @@
         fieldModel: fieldModel
       });
 
+      // Create the in-place editor's aural view — for screen reader support.
+      var fieldAuralView = new Drupal.quickedit.FieldAuralView({
+        el: $(fieldModel.get('el')),
+        model: fieldModel
+      });
+
       // Create in-place editor's toolbar for this field — stored inside the
       // entity toolbar, the entity toolbar will position itself appropriately
       // above (or below) the edited element.
@@ -294,6 +312,7 @@
       fieldModel.editorView = editorView;
       fieldModel.toolbarView = toolbarView;
       fieldModel.decorationView = decorationView;
+      fieldModel.fieldAuralView = fieldAuralView;
     },
 
     /**
@@ -323,6 +342,10 @@
       // because that would remove the field itself.
       fieldModel.editorView.remove();
       delete fieldModel.editorView;
+
+      // Unbind event handlers; delete aural view.
+      fieldModel.fieldAuralView.remove();
+      delete fieldModel.fieldAuralView;
     },
 
     /**
diff --git a/core/modules/quickedit/js/views/EntityToolbarView.js b/core/modules/quickedit/js/views/EntityToolbarView.js
index 61de7bb..949927c 100644
--- a/core/modules/quickedit/js/views/EntityToolbarView.js
+++ b/core/modules/quickedit/js/views/EntityToolbarView.js
@@ -83,8 +83,8 @@
         if ($body.children('#quickedit-entity-toolbar').length === 0) {
           $body.append(this.$el);
         }
-        // The fence will define a area on the screen that the entity toolbar
-        // will be position within.
+        // The fence will define an area on the screen that the entity toolbar
+        // will be positioned within.
         if ($body.children('#quickedit-toolbar-fence').length === 0) {
           this.$fence = $(Drupal.theme('quickeditEntityToolbarFence'))
             .css(Drupal.displace())
@@ -406,12 +406,17 @@
               type: 'submit',
               classes: 'action-save quickedit-button icon',
               attributes: {
-                'aria-hidden': true
+                'aria-hidden': true,
+                'tabindex': '0'
               }
             },
             {
               label: Drupal.t('Close'),
-              classes: 'action-cancel quickedit-button icon icon-close icon-only'
+              classes: 'action-cancel quickedit-button icon icon-close icon-only',
+              attributes: {
+                'tabindex': '0',
+                'aria-label': Drupal.t('Cancel in-place editing')
+              }
             }
           ]
         }));
@@ -469,9 +474,18 @@
         label = Drupal.checkPlain(entityLabel);
       }
 
+      // Label the toolbar.
       this.$el
+        .attr('aria-label', Drupal.t('Quick edit controls for @entity', {'@entity': entityLabel}))
         .find('.quickedit-toolbar-label')
         .html(label);
+
+      // Label the save button so that it has context.
+      var changeFieldsCount = this.model.get('fields').where({isChanged: true}).length;
+      var labelArgs = {'@fields': Drupal.formatPlural(changeFieldsCount, '@count field', '@count fields')};
+      this.$el
+        .find('.quickedit-toolbar-entity [type="submit"]')
+        .attr('aria-label', Drupal.t('Save changes to @fields', labelArgs));
     },
 
     /**
diff --git a/core/modules/quickedit/js/views/FieldAuralView.js b/core/modules/quickedit/js/views/FieldAuralView.js
new file mode 100644
index 0000000..13880f1
--- /dev/null
+++ b/core/modules/quickedit/js/views/FieldAuralView.js
@@ -0,0 +1,65 @@
+/**
+ * @file
+ * A Backbone View that adds screen reader support to in-place editors.
+ */
+
+(function (Backbone, Drupal) {
+
+  "use strict";
+
+  /**
+   * Reacts to field model changes by announcing the changes in a way that screen
+   * reading user agents will convey.
+   */
+  Drupal.quickedit.FieldAuralView = Backbone.View.extend(/** @lends Drupal.quickedit.FieldAuralView# */{
+
+    /**
+     * @constructs
+     *
+     * @augments Backbone.View
+     */
+    initialize: function () {
+      this.listenTo(this.model, 'change:state', this.stateChange);
+    },
+
+    /**
+     * @inheritdoc
+     */
+    remove: function () {
+      // The el property is the field, which should not be removed. Remove the
+      // pointer to it, then call Backbone.View.prototype.remove().
+      this.setElement();
+      Backbone.View.prototype.remove.call(this);
+    },
+
+    /**
+     * Determines the actions to take given a change of state.
+     *
+     * @param {Drupal.quickedit.FieldModel} fieldModel
+     * @param {string} state
+     *   The state of the associated field. One of
+     *   {@link Drupal.quickedit.FieldModel.states}.
+     */
+    stateChange: function (fieldModel, state) {
+      var to = state;
+      switch (to) {
+        case 'active':
+          // The user can now actually use the in-place editor.
+          this.announceActiveEditor();
+          break;
+
+        case 'invalid':
+          break;
+      }
+    },
+
+    /**
+     * Announces details of the field being edited in place.
+     */
+    announceActiveEditor: function () {
+      Drupal.announce(Drupal.t('Editing @field', {'@field': this.model.get('metadata').aria}), 'assertive');
+    }
+
+  });
+
+}(Backbone, Drupal));
diff --git a/core/modules/quickedit/js/views/FieldDecorationView.js b/core/modules/quickedit/js/views/FieldDecorationView.js
index b098f7f..6db7f30 100644
--- a/core/modules/quickedit/js/views/FieldDecorationView.js
+++ b/core/modules/quickedit/js/views/FieldDecorationView.js
@@ -40,6 +40,8 @@
 
       this.listenTo(this.model, 'change:state', this.stateChange);
       this.listenTo(this.model, 'change:isChanged change:inTempStore', this.renderChanged);
+
+      $(document).on('keypress.edit', this.onKeypress.bind(this));
     },
 
     /**
@@ -48,6 +50,7 @@
     remove: function () {
       // The el property is the field, which should not be removed. Remove the
       // pointer to it, then call Backbone.View.prototype.remove().
+      $(document).off('keypress.edit');
       this.setElement();
       Backbone.View.prototype.remove.call(this);
     },
@@ -150,6 +153,17 @@
     },
 
     /**
+     * Triggers a click event on fields when the enter key is pressed on them.
+     *
+     * @param jQuery event
+     */
+    onKeypress: function (event) {
+      if (event.target === this.el && event.keyCode === 13) {
+        this.onClick(event);
+      }
+    },
+
+    /**
      * Transition to 'activating' stage.
      *
      * @param {jQuery.Event} event
@@ -166,6 +180,10 @@
      */
     decorate: function () {
       this.$el.addClass('quickedit-candidate quickedit-editable');
+      this.$el.attr({
+        'role': 'button',
+        'tabindex': '0'
+      });
     },
 
     /**
@@ -173,6 +191,7 @@
      */
     undecorate: function () {
       this.$el.removeClass('quickedit-candidate quickedit-editable quickedit-highlighted quickedit-editing');
+      this.$el.removeAttr('tabindex role');
     },
 
     /**
diff --git a/core/modules/quickedit/quickedit.libraries.yml b/core/modules/quickedit/quickedit.libraries.yml
index 7c81563..0718db3 100644
--- a/core/modules/quickedit/quickedit.libraries.yml
+++ b/core/modules/quickedit/quickedit.libraries.yml
@@ -18,6 +18,7 @@ quickedit:
     js/views/ContextualLinkView.js: {}
     js/views/FieldToolbarView.js: {}
     js/views/EditorView.js: {}
+    js/views/FieldAuralView.js: {}
     # Other.
     js/theme.js: {}
   css:
@@ -35,6 +36,7 @@ quickedit:
     - core/jquery.ui.position
     - core/drupal
     - core/drupal.displace
+    - core/drupal.tabbingmanager
     - core/drupal.form
     - core/drupal.ajax
     - core/drupal.debounce
