diff --git modules/overlay/overlay-parent.js modules/overlay/overlay-parent.js
index 607b8b4..a14d8c8 100644
--- modules/overlay/overlay-parent.js
+++ modules/overlay/overlay-parent.js
@@ -7,11 +7,18 @@
  */
 Drupal.behaviors.overlayParent = {
   attach: function (context, settings) {
+    if (Drupal.overlay.isOpen) {
+      Drupal.overlay.applyARIAPresentation(context);
+    }
+
     if (this.processed) {
       return;
     }
     this.processed = true;
 
+    // Initiate the overlay ARIA buffer.
+    Drupal.overlay.updateARIABuffer();
+
     $(window)
       // When the hash (URL fragment) changes, open the overlay if needed.
       .bind('hashchange.drupal-overlay', $.proxy(Drupal.overlay, 'eventhandlerOperateByURLFragment'))
@@ -50,7 +57,12 @@ Drupal.overlay = Drupal.overlay || {
   isOpen: false,
   isOpening: false,
   isClosing: false,
-  isLoading: false
+  isLoading: false,
+  // Properties used to determine ARIA presentation role.
+  _linkables: {'a': true, 'area': true},
+  _formElements: {'input': true, 'select': true, 'textarea': true, 'button': true, 'object': true},
+  _blocks: {'address': true, 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, 'p': true, 'li': true, 'td': true, 'th': true},
+  _fallbackMessage: Drupal.t('You have left the overlay and are now browsing the underlying page.')
 };
 
 Drupal.overlay.prototype = {};
@@ -79,6 +91,9 @@ Drupal.overlay.open = function (url) {
   this.isOpening = false;
   this.isOpen = true;
   $(document.documentElement).addClass('overlay-open');
+  this.applyARIAPresentation();
+
+  $('#skip-link a').attr('href', '#overlay-main-content');
 
   // Allow other scripts to respond to this event.
   $(document).trigger('drupalOverlayOpen');
@@ -90,8 +105,17 @@ Drupal.overlay.open = function (url) {
  * Create the underlying markup and behaviors for the overlay.
  */
 Drupal.overlay.create = function () {
-  this.$container = $(Drupal.theme('overlayContainer'))
-    .appendTo(document.body);
+  this.$container = $(Drupal.theme('overlayContainer'));
+  // Append the overlay container near the top of the page.
+  if ($('.region-page-top .overlay-displace-top').length) {
+    this.$container.insertAfter('.region-page-top');
+  }
+  else if ($('#skip-link').length) {
+    this.$container.insertAfter('#skip-link');
+  }
+  else {
+    this.$container.prependTo(document.body);
+  }
 
   // Overlay uses transparent iframes that cover the full parent window.
   // When the overlay is open the scrollbar of the parent window is hidden.
@@ -101,9 +125,11 @@ Drupal.overlay.create = function () {
   // background. When the page is loaded the active and inactive iframes
   // are switched.
   this.activeFrame = this.$iframeA = $(Drupal.theme('overlayElement'))
+    .attr({ 'id': 'overlay-main-content', 'tabindex': -1, 'role': 'presentation' })
     .appendTo(this.$container);
 
   this.inactiveFrame = this.$iframeB = $(Drupal.theme('overlayElement'))
+    .attr({ 'tabindex': -1, 'role': 'presentation' })
     .appendTo(this.$container);
 
   this.$iframeA.bind('load.drupal-overlay', { self: this.$iframeA[0], sibling: this.$iframeB }, $.proxy(this, 'loadChild'));
@@ -121,8 +147,7 @@ Drupal.overlay.create = function () {
     .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRefreshPage'))
     .bind('drupalOverlayBeforeClose' + eventClass +
           ' drupalOverlayBeforeLoad' + eventClass +
-          ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent'))
-    .bind('keydown' + eventClass, $.proxy(this, 'eventhandlerRestrictKeyboardNavigation'));
+          ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent'));
 
   if ($('.overlay-displace-top, .overlay-displace-bottom').length) {
     $(document)
@@ -158,8 +183,7 @@ Drupal.overlay.load = function (url) {
   // entry using URL fragments.
   iframeDocument.location.replace(url);
 
-  // Immediately move the focus to the iframe.
-  this.inactiveFrame.focus();
+  this.updateARIABuffer();
   return true;
 };
 
@@ -189,6 +213,14 @@ Drupal.overlay.close = function () {
   // Restore the original document title.
   document.title = this.originalTitle;
 
+  $('#skip-link a').attr('href', '#main-content');
+
+  // Move focus to the element that was active right before opening the overlay.
+  if (this.lastActiveElement) {
+    $(this.lastActiveElement).focus();
+    this.lastActiveElement = null;
+  }
+
   // Allow other scripts to respond to this event.
   $(document).trigger('drupalOverlayClose');
 
@@ -206,8 +238,10 @@ Drupal.overlay.close = function () {
  */
 Drupal.overlay.destroy = function () {
   $([document, window]).unbind('.drupal-overlay-open');
-  this.$iframeA.unbind('.drupal-overlay');
-  this.$iframeB.unbind('.drupal-overlay');
+  // Remove iframes first to make sure they are removed from the ARIA buffer;
+  // FF does not update the virtual buffer correctly.
+  this.$iframeA.unbind('.drupal-overlay').remove();
+  this.$iframeB.unbind('.drupal-overlay').remove();
   this.$container.remove();
 
   this.$container = null;
@@ -271,7 +305,7 @@ Drupal.overlay.loadChild = function (event) {
 
   this.isLoading = false;
   $(document.documentElement).removeClass('overlay-loading');
-  event.data.sibling.removeClass('overlay-active');
+  event.data.sibling.removeClass('overlay-active').removeAttr('id').attr({ 'tabindex': -1, 'role': 'presentation' });
 
   // Only continue when overlay is still open and not closing.
   if (this.isOpen && !this.isClosing) {
@@ -280,10 +314,17 @@ Drupal.overlay.loadChild = function (event) {
       // Replace the document title with title of iframe.
       document.title = iframeWindow.document.title;
 
+      var overlayTitle = Drupal.t('Overlay containing the administrative page @title', { '@title': iframeWindow.jQuery('#overlay-title').text() });
+      // Add a label to the dialog (container has the dialog role).
+      this.$container.attr('aria-label', overlayTitle);
+
       this.activeFrame = $(iframe)
         .addClass('overlay-active')
-        // Add a title attribute to the iframe for accessibility.
-        .attr('title', Drupal.t('@title dialog', { '@title': iframeWindow.jQuery('#overlay-title').text() }));
+        // Activate iframe and add a title attribute to the iframe in case the
+        // dialog role is not supported.
+        .removeAttr('role')
+        .attr({ 'id': 'overlay-main-content', 'tabindex': 0, 'title': overlayTitle });
+
       this.inactiveFrame = event.data.sibling;
 
       // Load an empty document into the inactive iframe.
@@ -291,6 +332,10 @@ Drupal.overlay.loadChild = function (event) {
 
       // Allow other scripts to respond to this event.
       $(document).trigger('drupalOverlayLoad');
+      
+      this.updateARIABuffer();
+      this.activeFrame.focus();
+      iframeWindow.jQuery('a:first').focus();
     }
     else {
       window.location = iframeWindow.location.href.replace(/([?&]?)render=overlay&?/g, '$1').replace(/\?$/, '');
@@ -463,20 +508,39 @@ Drupal.overlay.eventhandlerOverrideLink = function (event) {
     return;
   }
 
+  var target = $target[0];
+  var href = target.href;
+
   // Close the overlay when the link contains the overlay-close class.
   if ($target.hasClass('overlay-close')) {
-    // Clearing the overlay URL fragment will close the overlay.
-    $.bbq.removeState('overlay');
+    $target.attr({ 'href': window.location.href.replace(window.location.hash, ''), 'target': '_parent' });
+    if (event.button == 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
+      // Clearing the overlay URL fragment will close the overlay.
+      $.bbq.removeState('overlay');
+      event.preventDefault();
+    }
     return;
   }
 
-  var target = $target[0];
-  var href = target.href;
   // Only handle links that have an href attribute and use the http(s) protocol.
   if (href != undefined && href != '' && target.protocol.match(/^https?\:/)) {
-    var anchor = href.replace(target.ownerDocument.location.href, '');
-    // Skip anchor links.
-    if (anchor.length == 0 || anchor.charAt(0) == '#') {
+    var anchor = href.replace(target.ownerDocument.location.href.replace(target.ownerDocument.location.hash, ''), '');
+    // Skip links identical to current location.
+    if (!anchor.length) {
+      return;
+    }
+    else if (anchor.charAt(0) == '#') {
+      // Mimic default anchor handling but don't change document's location.
+      if (this.isOpen && target.ownerDocument === document) {
+        $anchor = $(anchor);
+        if ($anchor.length && $anchor.closest('#overlay-container, .overlay-displace-top, .overlay-displace-bottom').length) {
+          if ($anchor.is(':focusable')) {
+            $anchor.focus();
+          }
+          $(window).scrollTop($anchor.position().top);
+        }
+        event.preventDefault();
+      }
       return;
     }
     // Open admin links in the overlay.
@@ -491,6 +555,8 @@ Drupal.overlay.eventhandlerOverrideLink = function (event) {
       // pressing the ALT, CTRL, META (Command key on the Macintosh keyboard)
       // or SHIFT key.
       if (event.button == 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
+        // Store the clicked link as the last active element.
+        this.lastActiveElement = target;
         // Redirect to a fragmentized href. This will trigger a hashchange event.
         this.redirect(href);
         // Prevent default action and further propagation of the event.
@@ -625,37 +691,6 @@ Drupal.overlay.eventhandlerRefreshPage = function (event) {
 };
 
 /**
- * Event handler: makes sure that when the overlay is open no elements (except
- * for elements inside any displaced elements) of the parent document are
- * reachable through keyboard (TAB) navigation.
- *
- * @param event
- *   Event being triggered, with the following restrictions:
- *   - event.type: keydown
- *   - event.currentTarget: document
- */
-Drupal.overlay.eventhandlerRestrictKeyboardNavigation = function (event) {
-  if (!this.$tabbables) {
-    this.$tabbables = $(':tabbable');
-  }
-
-  if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) {
-    // Whenever the focus is not inside the overlay (or a displaced element)
-    // move the focus along until it is.
-    var direction = event.shiftKey ? -1 : 1;
-    var current = this.$tabbables.index(event.target);
-    var $allowedParent = '#overlay-container, .overlay-displace-top, .overlay-displace-bottom';
-    if (current != -1 && this.$tabbables[current + direction] && !this.$tabbables.eq(current + direction).closest($allowedParent).length) {
-      while (this.$tabbables[current + direction] && !this.$tabbables.eq(current + direction).closest($allowedParent).length) {
-        current = current + direction;
-      }
-      // Move focus.
-      this.$tabbables.eq(current).focus();
-    }
-  }
-};
-
-/**
  * Event handler: dispatches events to the overlay document.
  *
  * @param event
@@ -813,6 +848,130 @@ Drupal.overlay.getDisplacement = function (region) {
 };
 
 /**
+ * Updates ARIA buffer.
+ *
+ * @see http://juicystudio.com/article/improving-ajax-applications-for-jaws-users.php
+ */
+Drupal.overlay.updateARIABuffer = function () {
+  if (!Drupal.overlay.$ariabuffer) {
+    Drupal.overlay.$ariabuffer = $('<input id="overlay-ariabuffer" name="overlay-ariabuffer" type="hidden" value="0"/>').appendTo(document.body);
+  }
+  Drupal.overlay.ariabuffer = (Drupal.overlay.ariabuffer == 1) ? 0 : 1;
+  Drupal.overlay.$ariabuffer.val(Drupal.overlay.ariabuffer);
+};
+
+/**
+ * Applies the ARIA presentation role to a set of elements.
+ *
+ * @param context
+ *   A DOM element or jQuery object to which to apply the presentation role.
+ *   Defaults to the document body.
+ */
+Drupal.overlay.applyARIAPresentation = function (context) {
+  context = $(context || document.body);
+  context.each(function () {
+    if (context && $(this).closest('#overlay-container, #overlay-ariabuffer, .overlay-displace-top, .overlay-displace-bottom').length) {
+      return;
+    }
+
+    var i, len;
+    len = this.childNodes.length;
+    for (i = 0; i < len; i++) {
+      if (this.childNodes[i].nodeType === 1) {
+        Drupal.overlay._applyARIAPresentation(this.childNodes[i]);
+      }
+    }
+  });
+
+  Drupal.overlay.updateARIABuffer();
+  $(document).bind('drupalOverlayClose.drupal-overlay', Drupal.overlay.updateARIABuffer);
+};
+
+/**
+ * Applies the ARIA presentation role to an element, where appropriate.
+ *
+ * @param element
+ *   The DOM element to which the presentation role should be applied. Note that
+ *   this function is recursive, so the element and all its children will
+ *   be examined to determine whether they should receive the presentation role.
+ */
+Drupal.overlay._applyARIAPresentation = function (element) {
+  var i, len, childNodes;
+  var self = Drupal.overlay;
+  var id = element.getAttribute('id');
+  if (id === 'skip-link' || id === 'overlay-container' || id === 'overlay-ariabuffer'
+      || (' ' + element.className + ' ').indexOf(' overlay-displace-') > -1) {
+    return;
+  }
+
+  var nodeName = element.nodeName.toLowerCase();
+  var tabIndex = element.getAttribute('tabIndex');
+  var tabbable = (tabIndex !== null && tabIndex >= 0)
+      || (self._linkables[nodeName] && element.href)
+      || (self._formElements[nodeName] && !element.disabled);
+  if (tabbable) {
+    element.setAttribute('tabIndex', -1);
+  }
+
+  var role = element.getAttribute('role');
+  element.setAttribute('role', 'presentation');
+
+  if (element.childNodes) {
+    childNodes = element.childNodes;
+    len = childNodes.length;
+    for (i = 0; i < len; i++) {
+      if (childNodes[i].nodeType === 1) {
+        Drupal.overlay._applyARIAPresentation(childNodes[i]);
+      }
+    }
+  }
+
+  // Only continue if this element hasn't been processed before.
+  if (tabIndex !== '-1' && role !== 'presentation') {
+    // In case ARIA is not supported, add some textual hints to the underlying
+    // page to help screen readers understand that the page is being displayed
+    // beneath the overlay.
+    if (self._blocks[nodeName]) {
+      var $fallback = $('<span class="element-invisible overlay-aria-fallback" role="presentation">' + self._fallbackMessage + '</span>').prependTo(element);
+    }
+
+    // Restore attributes when the overlay closes.
+    $(document).bind('drupalOverlayClose.drupal-overlay', {'tabbable': tabbable, 'tabIndex': tabIndex, 'element': element, 'role': role, '$fallback': $fallback}, Drupal.overlay.restoreAttributes);
+  }
+};
+
+/**
+ * Event handler; restores an element's original ARIA role on overlay close.
+ */
+Drupal.overlay.restoreAttributes = function (event) {
+  var tabbable = event.data.tabbable,
+      tabIndex = event.data.tabIndex,
+      element = event.data.element,
+      role = event.data.role,
+      $fallback = event.data.$fallback;
+      
+  if (tabbable) {
+    if (tabIndex !== null) {
+      element.setAttribute('tabIndex', tabIndex);
+    }
+    else {
+      element.removeAttribute('tabIndex');
+    }
+  }
+
+  if (role !== null) {
+    element.setAttribute('role', tabIndex);
+  }
+  else {
+    element.removeAttribute('role');
+  }
+
+  if ($fallback) {
+    $fallback.remove();
+  }
+}
+
+/**
  * Theme function to create the overlay iframe element.
  */
 Drupal.theme.prototype.overlayContainer = function () {
@@ -823,7 +982,7 @@ Drupal.theme.prototype.overlayContainer = function () {
  * Theme function to create an overlay iframe element.
  */
 Drupal.theme.prototype.overlayElement = function (url) {
-  return '<iframe class="overlay-element" frameborder="0" scrolling="auto" allowtransparency="true" role="document"></iframe>';
+  return '<iframe class="overlay-element" frameborder="0" scrolling="auto" allowtransparency="true"></iframe>';
 };
 
 })(jQuery);
diff --git modules/overlay/overlay.module modules/overlay/overlay.module
index eb1bb49..4bbba43 100644
--- modules/overlay/overlay.module
+++ modules/overlay/overlay.module
@@ -304,6 +304,10 @@ function overlay_preprocess_html(&$variables) {
     // Add overlay class, so themes can react to being displayed in the overlay.
     $variables['classes_array'][] = 'overlay';
   }
+  global $user;
+  if (overlay_get_mode() != 'none' && $user->uid) {
+    $variables['overlay_disable'] = l(t('Disable the overlay'), 'user/' . $user->uid . '/edit', array('attributes' => array('class' => array('overlay-exclude'))));
+  }
 }
 
 /**
diff --git modules/system/html.tpl.php modules/system/html.tpl.php
index 7ef8fc2..2c254bf 100644
--- modules/system/html.tpl.php
+++ modules/system/html.tpl.php
@@ -47,6 +47,11 @@
   <div id="skip-link">
     <a href="#main-content"><?php print t('Skip to main content'); ?></a>
   </div>
+  <?php
+  if (isset($overlay_disable)) {
+    print '<div class="element-invisible" id="overlay-disable">' . $overlay_disable . '</div>';
+  }
+  ?>
   <?php print $page_top; ?>
   <?php print $page; ?>
   <?php print $page_bottom; ?>
