diff --git a/app/code/Magento/AdminNotification/Ui/Component/DataProvider/DataProvider.php b/app/code/Magento/AdminNotification/Ui/Component/DataProvider/DataProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..296fcf5c461ad4521fed496e376ed9289f50cf0c
--- /dev/null
+++ b/app/code/Magento/AdminNotification/Ui/Component/DataProvider/DataProvider.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\AdminNotification\Ui\Component\DataProvider;
+
+use Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\SynchronizedFactory;
+
+/**
+ * Class DataProvider
+ */
+class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
+{
+    /**
+     * DataProvider constructor.
+     * @param string $name
+     * @param string $primaryFieldName
+     * @param string $requestFieldName
+     * @param SynchronizedFactory $messageCollectionFactory
+     * @param array $meta
+     * @param array $data
+     */
+    public function __construct(
+        $name,
+        $primaryFieldName,
+        $requestFieldName,
+        SynchronizedFactory $messageCollectionFactory,
+        array $meta = [],
+        array $data = []
+    ) {
+        $this->collection = $messageCollectionFactory->create();
+        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
+    }
+}
diff --git a/app/code/Magento/AdminNotification/composer.json b/app/code/Magento/AdminNotification/composer.json
index 0a29908b77f6f4f6511fe5671702217ef95ceb90..527268df36b41efe5078528138342a03fa33745d 100644
--- a/app/code/Magento/AdminNotification/composer.json
+++ b/app/code/Magento/AdminNotification/composer.json
@@ -7,6 +7,7 @@
         "magento/module-backend": "100.2.*",
         "magento/module-media-storage": "100.2.*",
         "magento/framework": "100.2.*",
+        "magento/module-ui": "100.2.*",
         "lib-libxml": "*"
     },
     "type": "magento2-module",
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml
index e3d32f17b6356bc974627b0daaba5dca5c467eea..780e48378a3a4069c29b50153eb6ca013f36147e 100644
--- a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml
+++ b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml
@@ -8,11 +8,7 @@
 <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
     <body>
         <referenceContainer name="notifications">
-            <block class="Magento\AdminNotification\Block\System\Messages"
-                   name="system_messages"
-                   as="system_messages"
-                   before="-"
-                   template="Magento_AdminNotification::system/messages.phtml"/>
+            <uiComponent name="notification_area"/>
             <block class="Magento\AdminNotification\Block\System\Messages\UnreadMessagePopup"
                    name="unread_system_messages"
                    as="unread_system_messages"
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/ui_component/notification_area.xml b/app/code/Magento/AdminNotification/view/adminhtml/ui_component/notification_area.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e0149fff714deaa5fc95910169ec92a26e4c673c
--- /dev/null
+++ b/app/code/Magento/AdminNotification/view/adminhtml/ui_component/notification_area.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
+    <argument name="data" xsi:type="array">
+        <item name="js_config" xsi:type="array">
+            <item name="provider" xsi:type="string">notification_area.notification_area_data_source</item>
+            <item name="deps" xsi:type="string">notification_area.notification_area_data_source</item>
+        </item>
+        <item name="spinner" xsi:type="string">columns</item>
+    </argument>
+    <dataSource name="notification_area_data_source">
+        <argument name="dataProvider" xsi:type="configurableObject">
+            <argument name="class" xsi:type="string">Magento\AdminNotification\Ui\Component\DataProvider\DataProvider</argument>
+            <argument name="name" xsi:type="string">notification_area_data_source</argument>
+            <argument name="primaryFieldName" xsi:type="string">identity</argument>
+            <argument name="requestFieldName" xsi:type="string">identity</argument>
+            <argument name="data" xsi:type="array">
+                <item name="config" xsi:type="array">
+                    <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
+                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
+                    <item name="storageConfig" xsi:type="array">
+                        <item name="indexField" xsi:type="string">identity</item>
+                    </item>
+                </item>
+            </argument>
+        </argument>
+    </dataSource>
+    <columns name="columns">
+        <argument name="data" xsi:type="array">
+            <item name="config" xsi:type="array">
+                <item name="component" xsi:type="string">Magento_AdminNotification/js/grid/listing</item>
+                <item name="template" xsi:type="string">Magento_AdminNotification/grid/listing</item>
+            </item>
+        </argument>
+        <column name="created_at">
+            <argument name="data" xsi:type="array">
+                <item name="config" xsi:type="array">
+                    <item name="component" xsi:type="string">Magento_AdminNotification/js/grid/columns/message</item>
+                    <item name="label" xsi:type="string" translate="true"/>
+                    <item name="dataType" xsi:type="string">text</item>
+                    <item name="sorting" xsi:type="string">asc</item>
+                    <item name="sortOrder" xsi:type="number">30</item>
+                </item>
+            </argument>
+        </column>
+    </columns>
+</listing>
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js
new file mode 100644
index 0000000000000000000000000000000000000000..aa5477ebafcf0df511981f2f874c7a1e4c7081bb
--- /dev/null
+++ b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js
@@ -0,0 +1,46 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+define([
+    'Magento_Ui/js/grid/columns/column',
+    'underscore'
+], function (Column, _) {
+    'use strict';
+
+    return Column.extend({
+        defaults: {
+            bodyTmpl: 'Magento_AdminNotification/grid/cells/message',
+            messageIndex: 'text',
+            fieldClass: {
+                message: true,
+                'message-warning': false,
+                'message-progress': false,
+                'message-success': false,
+                'message-error': false
+            },
+            statusMap: {
+                0: 'info',
+                1: 'progress',
+                2: 'success',
+                3: 'error'
+            }
+        },
+
+        /** @inheritdoc */
+        getLabel: function (record) {
+            return record[this.messageIndex];
+        },
+
+        /** @inheritdoc */
+        getFieldClass: function ($row) {
+            var status = this.statusMap[$row.status] || 'warning',
+                result = {};
+
+            result['message-' + status] = true;
+            result = _.extend({}, this.fieldClass, result);
+
+            return result;
+        }
+    });
+});
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js
new file mode 100644
index 0000000000000000000000000000000000000000..1fbda8d0196ca1fa9e1b58f40d0804892bf70891
--- /dev/null
+++ b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js
@@ -0,0 +1,63 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+    'Magento_Ui/js/grid/listing',
+    'Magento_Ui/js/lib/spinner',
+    'jquery'
+], function (Listing, loader, $) {
+    'use strict';
+
+    return Listing.extend({
+        defaults: {
+            imports: {
+                totalRecords: '${ $.provider }:data.totalRecords'
+            },
+            selectors: {
+                collapsible: '.message-system-collapsible',
+                messages: '.message-system'
+            }
+        },
+
+        /** @inheritdoc */
+        initObservable: function () {
+            this._super()
+                .track({
+                    totalRecords: 0
+                });
+
+            return this;
+        },
+
+        /** @inheritdoc */
+        showLoader: function () {
+            if (!this.source.firstLoad) {
+                this.fixLoaderHeight();
+                this._super();
+            }
+        },
+
+        /**
+         * Calculates loader height
+         *
+         * @param {Boolean} [closed]
+         */
+        fixLoaderHeight: function (closed) {
+            var $messagesBlock = $(this.selectors.messages),
+                $collapsibleBlock = $(this.selectors.collapsible),
+                resultHeight = 0;
+
+            if ($messagesBlock.length) {
+                resultHeight += $messagesBlock.outerHeight();
+            }
+
+            if ($collapsibleBlock.length && $collapsibleBlock.is(':visible') && !closed) {
+                resultHeight += $collapsibleBlock.outerHeight();
+            }
+
+            loader.get(this.name).height(resultHeight);
+        }
+    });
+});
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html
new file mode 100644
index 0000000000000000000000000000000000000000..869842e8ee87bcc9465d3763dc610c95361cad72
--- /dev/null
+++ b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html
@@ -0,0 +1,8 @@
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<div css="$col.getFieldClass($row())"
+     html="$col.getLabel($row())"/>
\ No newline at end of file
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html
new file mode 100644
index 0000000000000000000000000000000000000000..80f2f4f2423fbcad5ba9b8e030e56c806db5ef9a
--- /dev/null
+++ b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html
@@ -0,0 +1,32 @@
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<div id="system_messages" class="message-system" collapsible visible="totalRecords">
+    <div class="message-system-inner" outerClick="fixLoaderHeight.bind($data, true)">
+        <div class="message-system-short">
+            <button class="message-system-action-dropdown" toggleCollapsible>
+                <span>
+                    <translate args="'System Messages'"/>:
+                    <text args="totalRecords"/>
+                </span>
+            </button>
+            <div class="message-system-short-wrapper" if="rows[0]" repeat="foreach: [rows[0]], item: '$row'" visible="!$collapsible.opened()">
+                <fastForEach args="data: getVisible(), as: '$col'" >
+                    <render args="$col.getBody()"/>
+                </fastForEach>
+            </div>
+        </div>
+        <div class="message-system-collapsible">
+            <ul class="message-system-list">
+                <li repeat="foreach: rows, item: '$row'">
+                    <fastForEach args="data: getVisible(), as: '$col'" >
+                        <render args="$col.getBody()"/>
+                    </fastForEach>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
diff --git a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml
index 991d9a36d18e04c7e8b17744dd3b2d32981fdc18..2f8feb52db5ffc071055bebfb436b449331dbb1c 100644
--- a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml
+++ b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml
@@ -101,7 +101,33 @@
                 </block>
             </container>
             <container name="product.info.media" htmlTag="div" htmlClass="product media" after="product.info.main">
+                <block class="Magento\Framework\View\Element\Template" name="skip_gallery_after.target" before="skip_gallery_before.wrapper" template="Magento_Theme::html/skiptarget.phtml">
+                    <arguments>
+                        <argument name="target_id" xsi:type="string">gallery-prev-area</argument>
+                    </arguments>
+                </block>
+                <container name="skip_gallery_before.wrapper" htmlTag="div" htmlClass="action-skip-wrapper">
+                    <block class="Magento\Framework\View\Element\Template" before="product.info.media.image" name="skip_gallery_before" template="Magento_Theme::html/skip.phtml">
+                        <arguments>
+                            <argument name="target" xsi:type="string">gallery-next-area</argument>
+                            <argument name="label" translate="true" xsi:type="string">Skip to the end of the images gallery</argument>
+                        </arguments>
+                    </block>
+                </container>
                 <block class="Magento\Catalog\Block\Product\View\Gallery" name="product.info.media.image" template="product/view/gallery.phtml"/>
+                <container name="skip_gallery_after.wrapper" htmlTag="div" htmlClass="action-skip-wrapper">
+                    <block class="Magento\Framework\View\Element\Template" after="product.info.media.image" name="skip_gallery_after" template="Magento_Theme::html/skip.phtml">
+                        <arguments>
+                            <argument name="target" xsi:type="string">gallery-prev-area</argument>
+                            <argument name="label" translate="true" xsi:type="string">Skip to the beginning of the images gallery</argument>
+                        </arguments>
+                    </block>
+                </container>
+                <block class="Magento\Framework\View\Element\Template" name="skip_gallery_before.target" after="skip_gallery_after.wrapper" template="Magento_Theme::html/skiptarget.phtml">
+                    <arguments>
+                        <argument name="target_id" xsi:type="string">gallery-next-area</argument>
+                    </arguments>
+                </block>
             </container>
             <block class="Magento\Catalog\Block\Product\View\Description" name="product.info.details" template="product/view/details.phtml" after="product.info.media">
                 <block class="Magento\Catalog\Block\Product\View\Description" name="product.info.description" template="product/view/attribute.phtml" group="detailed_info">
diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml
index b63acf736f99f4102c9bdd89568959cd1e36ba7f..3238f68401f178530ea3dc684d518bca619ab847 100644
--- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml
+++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml
@@ -21,7 +21,7 @@
         <?php endif; ?>
     </div>
 <?php else: ?>
-    <div class="message error">
+    <div role="alert" class="message error">
         <div>
             <?php /* @escapeNotVerified */ echo __('We can\'t find any items matching these search criteria.');?> <a href="<?php /* @escapeNotVerified */ echo $block->getFormUrl(); ?>"><?php /* @escapeNotVerified */ echo __('Modify your search.'); ?></a>
         </div>
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js
index 60691ab0e7b5a73bc4d3e58746be70f5ae5b070e..ddb62ceb37b37ef6bc7735c78f81367c5c6d4343 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js
@@ -260,11 +260,13 @@ define(
                         this.source.trigger('shippingAddress.custom_attributes.data.validate');
                     }
 
-                    if (this.source.get('params.invalid') ||
+                    if (emailValidationResult &&
+                        this.source.get('params.invalid') ||
                         !quote.shippingMethod().method_code ||
-                        !quote.shippingMethod().carrier_code ||
-                        !emailValidationResult
+                        !quote.shippingMethod().carrier_code
                     ) {
+                        this.focusInvalid();
+
                         return false;
                     }
 
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping.html
index 9078731a8558f6fd2e1fde6a9201fbffc0d1fce0..5049aac2bbc9b056be66344e8b21542a2323dd68 100644
--- a/app/code/Magento/Checkout/view/frontend/web/template/shipping.html
+++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping.html
@@ -120,7 +120,7 @@
                         <!-- ko if:  method.error_message -->
                         <tr class="row row-error">
                             <td class="col col-error" colspan="4">
-                                <div class="message error">
+                                <div role="alert" class="message error">
                                     <div data-bind="text: method.error_message"></div>
                                 </div>
                                 <span class="no-display">
@@ -141,7 +141,7 @@
                     <!-- /ko -->
                 </div>
                 <!-- ko if: errorValidationMessage().length > 0 -->
-                <div class="message notice">
+                <div role="alert" class="message notice">
                     <span><!-- ko text: errorValidationMessage()--><!-- /ko --></span>
                 </div>
                 <!-- /ko -->
diff --git a/app/code/Magento/Cookie/view/frontend/templates/html/notices.phtml b/app/code/Magento/Cookie/view/frontend/templates/html/notices.phtml
index cbf1d3ad2b231633b142127bdcf90df83d9f7bcf..f7aaf538ae96c210ae4aa7fdff0c84412207f948 100644
--- a/app/code/Magento/Cookie/view/frontend/templates/html/notices.phtml
+++ b/app/code/Magento/Cookie/view/frontend/templates/html/notices.phtml
@@ -9,12 +9,17 @@
 /** @var \Magento\Cookie\Block\Html\Notices $block */
 ?>
 <?php if ($this->helper(\Magento\Cookie\Helper\Cookie::class)->isUserNotAllowSaveCookie()): ?>
-    <div class="message global cookie" id="notice-cookie-block" style="display: none">
-        <div class="content">
+    <div role="alertdialog"
+         tabindex="-1"
+         class="message global cookie"
+         id="notice-cookie-block"
+         style="display: none;">
+        <div role="document" class="content" tabindex="0">
             <p>
                 <strong><?php /* @escapeNotVerified */ echo __('We use cookies to make your experience better.') ?></strong>
                 <span><?php /* @escapeNotVerified */ echo __('To comply with the new e-Privacy directive, we need to ask for your consent to set the cookies.') ?></span>
-                <?php /* @escapeNotVerified */ echo __('<a href="%1">Learn more</a>.', $block->getPrivacyPolicyLink()) ?></p>
+                <?php /* @escapeNotVerified */ echo __('<a href="%1">Learn more</a>.', $block->getPrivacyPolicyLink()) ?>
+            </p>
             <div class="actions">
                 <button id="btn-cookie-allow" class="action allow primary">
                     <span><?php /* @escapeNotVerified */ echo __('Allow Cookies');?></span>
diff --git a/app/code/Magento/ImportExport/view/adminhtml/templates/busy.phtml b/app/code/Magento/ImportExport/view/adminhtml/templates/busy.phtml
index cd77de6c78d861c318077a6a258f81fdada7ea63..5e6e0b8e9abf4e44aed7a92a58d603b5bde0d3f6 100644
--- a/app/code/Magento/ImportExport/view/adminhtml/templates/busy.phtml
+++ b/app/code/Magento/ImportExport/view/adminhtml/templates/busy.phtml
@@ -10,7 +10,7 @@
     </div><br>
     <div class="messages">
         <div class="message message-success success">
-            <div><?php /* @escapeNotVerified */ echo $block->getStatusMessage(); ?>hh</div>
+            <div><?php /* @escapeNotVerified */ echo $block->getStatusMessage(); ?></div>
         </div>
     </div>
 </div>
diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml
index 19b4002efb2a71289961b139cf41517c0c78e8ef..c8842b5b5be3a31f206e4cd9d98e0938a5342c2c 100644
--- a/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml
+++ b/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml
@@ -10,15 +10,19 @@
 ?>
 <?php $swatchData = $block->getSwatchData(); ?>
 <div class="swatch-attribute swatch-layered <?php /* @escapeNotVerified */ echo $swatchData['attribute_code'] ?>"
-     attribute-code="<?php /* @escapeNotVerified */ echo $swatchData['attribute_code'] ?>" attribute-id="<?php /* @escapeNotVerified */ echo $swatchData['attribute_id'] ?>">
+     attribute-code="<?php /* @escapeNotVerified */ echo $swatchData['attribute_code'] ?>"
+     attribute-id="<?php /* @escapeNotVerified */ echo $swatchData['attribute_id'] ?>">
     <div class="swatch-attribute-options clearfix">
         <?php foreach ($swatchData['options'] as $option => $label): ?>
-            <a href="<?php /* @escapeNotVerified */ echo $label['link'] ?>" class="swatch-option-link-layered">
+            <a href="<?php /* @escapeNotVerified */ echo $label['link'] ?>"
+               aria-label="<?php /* @escapeNotVerified */ echo $label['label'] ?>"
+               class="swatch-option-link-layered">
                 <?php if (isset($swatchData['swatches'][$option]['type'])) { ?>
                     <?php switch ($swatchData['swatches'][$option]['type']) {
                         case '3':
                             ?>
                             <div class="swatch-option <?php /* @escapeNotVerified */ echo $label['custom_style'] ?>"
+                                 tabindex="-1"
                                  option-type="3"
                                  option-id="<?php /* @escapeNotVerified */ echo $option ?>"
                                  option-label="<?php /* @escapeNotVerified */ echo $label['label'] ?>"
@@ -33,6 +37,7 @@
                             <?php $swatchImagePath = $block->getSwatchPath('swatch_image',
                             $swatchData['swatches'][$option]['value']); ?>
                             <div class="swatch-option image <?php /* @escapeNotVerified */ echo $label['custom_style'] ?>"
+                                 tabindex="-1"
                                  option-type="2"
                                  option-id="<?php /* @escapeNotVerified */ echo $option ?>"
                                  option-label="<?php /* @escapeNotVerified */ echo $label['label'] ?>"
@@ -43,6 +48,7 @@
                         case '1':
                             ?>
                             <div class="swatch-option color <?php /* @escapeNotVerified */ echo $label['custom_style'] ?>"
+                                 tabindex="-1"
                                  option-type="1"
                                  option-id="<?php /* @escapeNotVerified */ echo $option ?>"
                                  option-label="<?php /* @escapeNotVerified */ echo $label['label'] ?>"
@@ -54,6 +60,7 @@
                         default:
                             ?>
                                 <div class="swatch-option text <?php /* @escapeNotVerified */ echo $label['custom_style'] ?>"
+                                     tabindex="-1"
                                      option-type="0"
                                      option-id="<?php /* @escapeNotVerified */ echo $option ?>"
                                      option-label="<?php /* @escapeNotVerified */ echo $label['label'] ?>"
diff --git a/app/code/Magento/Swatches/view/frontend/web/css/swatches.css b/app/code/Magento/Swatches/view/frontend/web/css/swatches.css
index 8204fc7fc8f630f64ace7caa37567550354f62cf..3899a663788b0f308ce5bf4c7b0f8da4389c4245 100644
--- a/app/code/Magento/Swatches/view/frontend/web/css/swatches.css
+++ b/app/code/Magento/Swatches/view/frontend/web/css/swatches.css
@@ -210,6 +210,10 @@
     padding: 0 !important;
 }
 
+.swatch-option-link-layered:focus > div {
+    box-shadow: 0 0 3px 1px #68a8e0;
+}
+
 .swatch-option-tooltip-layered {
     width: 140px;
     position: absolute;
@@ -276,3 +280,9 @@
 .swatch-option-loading {
     content: url("../images/loader-2.gif");
 }
+
+.swatch-input {
+    left: -1000px;
+    position: absolute;
+    visibility: hidden;
+}
diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
index dfd4b2bb2da0ffcc19286dafd125be59d980f804..4e79c42dd6adbf4eb1f079f569ac3aeb77799241 100644
--- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
+++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
@@ -6,11 +6,57 @@
 define([
     'jquery',
     'underscore',
+    'mage/smart-keyboard-handler',
     'jquery/ui',
-    'jquery/jquery.parsequery'
-], function ($, _) {
+    'jquery/jquery.parsequery',
+    'mage/validation/validation'
+], function ($, _, keyboardHandler) {
     'use strict';
 
+    /**
+     * Extend form validation to support swatch accessibility
+     */
+    $.widget('mage.validation', $.mage.validation, {
+        /**
+         * Handle form with swatches validation. Focus on first invalid swatch block.
+         *
+         * @param {jQuery.Event} event
+         * @param {Object} validation
+         */
+        listenFormValidateHandler: function (event, validation) {
+            var swatchWrapper, firstActive, swatches, swatch, successList, errorList, firstSwatch;
+
+            this._superApply(arguments);
+
+            swatchWrapper = '.swatch-attribute-options';
+            swatches = $(event.target).find(swatchWrapper);
+
+            if (!swatches.length) {
+                return;
+            }
+
+            swatch = '.swatch-attribute';
+            firstActive = $(validation.errorList[0].element || []);
+            successList = validation.successList;
+            errorList = validation.errorList;
+            firstSwatch = $(firstActive).parent(swatch).find(swatchWrapper);
+
+            keyboardHandler.focus(swatches);
+
+            $.each(successList, function (index, item) {
+                $(item).parent(swatch).find(swatchWrapper).attr('aria-invalid', false);
+            });
+
+            $.each(errorList, function (index, item) {
+                $(item.element).parent(swatch).find(swatchWrapper).attr('aria-invalid', true);
+            });
+
+            if (firstSwatch.length) {
+                $(firstSwatch).focus();
+            }
+        }
+    });
+
     /**
      * Render tooltips by attributes (only to up).
      * Required element attributes:
@@ -117,6 +163,7 @@ define([
                 $element.hide();
                 clearTimeout(timer);
             });
+
             $(document).on('tap', function () {
                 $element.hide();
                 clearTimeout(timer);
@@ -169,6 +216,9 @@ define([
             //selector of product images gallery wrapper
             mediaGallerySelector: '[data-gallery-role=gallery-placeholder]',
 
+            // selector of category product tile wrapper
+            selectorProductTile: '.product-item',
+
             // number of controls to show (false or zero = show all)
             numberToShow: false,
 
@@ -178,6 +228,9 @@ define([
             // enable label for control
             enableControlLabel: true,
 
+            // control label id
+            controlLabelId: '',
+
             // text for more button
             moreButtonText: 'More',
 
@@ -191,7 +244,10 @@ define([
             mediaGalleryInitial: [{}],
 
             //
-            onlyMainImg: false
+            onlyMainImg: false,
+
+            // whether swatches are rendered in product list or on product page
+            inProductList: false
         },
 
         /**
@@ -246,7 +302,9 @@ define([
                     'img': $main.find('.product-image-photo').attr('src')
                 }];
             }
-            this.productForm = this.element.parents(this.options.selectorProduct).find('form:first');
+
+            this.productForm = this.element.parents(this.options.selectorProductTile).find('form:first');
+            this.inProductList = this.productForm.length > 0;
         },
 
         /**
@@ -264,9 +322,11 @@ define([
 
             $.each(this.options.jsonConfig.attributes, function () {
                 var item = this,
-                    options = $widget._RenderSwatchOptions(item),
+                    controlLabelId = 'option-label-' + item.code + '-' + item.id,
+                    options = $widget._RenderSwatchOptions(item, controlLabelId),
                     select = $widget._RenderSwatchSelect(item, chooseText),
                     input = $widget._RenderFormInput(item),
+                    listLabel = '',
                     label = '';
 
                 // Show only swatch controls
@@ -276,22 +336,32 @@ define([
 
                 if ($widget.options.enableControlLabel) {
                     label +=
-                        '<span class="' + classes.attributeLabelClass + '">' + item.label + '</span>' +
+                        '<span id="' + controlLabelId + '" class="' + classes.attributeLabelClass + '">' +
+                            item.label +
+                        '</span>' +
                         '<span class="' + classes.attributeSelectedOptionLabelClass + '"></span>';
                 }
 
-                if ($widget.productForm) {
+                if ($widget.inProductList) {
                     $widget.productForm.append(input);
                     input = '';
+                    listLabel = 'aria-label="' + item.label + '"';
+                } else {
+                    listLabel = 'aria-labelledby="' + controlLabelId + '"';
                 }
 
                 // Create new control
                 container.append(
-                    '<div class="' + classes.attributeClass + ' ' + item.code +
-                        '" attribute-code="' + item.code +
-                        '" attribute-id="' + item.id + '">' +
-                            label +
-                        '<div class="' + classes.attributeOptionsWrapper + ' clearfix">' +
+                    '<div class="' + classes.attributeClass + ' ' + item.code + '" ' +
+                         'attribute-code="' + item.code + '" ' +
+                         'attribute-id="' + item.id + '">' +
+                        label +
+                        '<div aria-activedescendant="" ' +
+                             'tabindex="0" ' +
+                             'aria-invalid="false" ' +
+                             'aria-required="true" ' +
+                             'role="listbox" ' + listLabel +
+                             'class="' + classes.attributeOptionsWrapper + ' clearfix">' +
                             options + select +
                         '</div>' + input +
                     '</div>'
@@ -336,10 +406,11 @@ define([
          * Render swatch options by part of config
          *
          * @param {Object} config
+         * @param {String} controlId
          * @returns {String}
          * @private
          */
-        _RenderSwatchOptions: function (config) {
+        _RenderSwatchOptions: function (config, controlId) {
             var optionConfig = this.options.jsonSwatchConfig[config.id],
                 optionClass = this.options.classes.optionClass,
                 moreLimit = parseInt(this.options.numberToShow, 10),
@@ -375,11 +446,17 @@ define([
                 thumb = optionConfig[id].hasOwnProperty('thumb') ? optionConfig[id].thumb : '';
                 label = this.label ? this.label : '';
                 attr =
+                    ' id="' + controlId + '-item-' + id + '"' +
+                    ' aria-checked="false"' +
+                    ' aria-describedby="' + controlId + '"' +
+                    ' tabindex="0"' +
                     ' option-type="' + type + '"' +
                     ' option-id="' + id + '"' +
                     ' option-label="' + label + '"' +
+                    ' aria-label="' + label + '"' +
                     ' option-tooltip-thumb="' + thumb + '"' +
-                    ' option-tooltip-value="' + value + '"';
+                    ' option-tooltip-value="' + value + '"' +
+                    ' role="option"';
 
                 if (!this.hasOwnProperty('products') || this.products.length <= 0) {
                     attr += ' option-empty="true"';
@@ -392,19 +469,19 @@ define([
                 } else if (type === 1) {
                     // Color
                     html += '<div class="' + optionClass + ' color" ' + attr +
-                        '" style="background: ' + value +
+                        ' style="background: ' + value +
                         ' no-repeat center; background-size: initial;">' + '' +
                         '</div>';
                 } else if (type === 2) {
                     // Image
                     html += '<div class="' + optionClass + ' image" ' + attr +
-                        '" style="background: url(' + value + ') no-repeat center; background-size: initial;">' + '' +
+                        ' style="background: url(' + value + ') no-repeat center; background-size: initial;">' + '' +
                         '</div>';
                 } else if (type === 3) {
                     // Clear
                     html += '<div class="' + optionClass + '" ' + attr + '></div>';
                 } else {
-                    // Defaualt
+                    // Default
                     html += '<div class="' + optionClass + '" ' + attr + '>' + label + '</div>';
                 }
             });
@@ -460,10 +537,9 @@ define([
                 'type="text" ' +
                 'value="" ' +
                 'data-selector="super_attribute[' + config.id + ']" ' +
-                'data-validate="{required:true}" ' +
+                'data-validate="{required: true}" ' +
                 'aria-required="true" ' +
-                'aria-invalid="true" ' +
-                'style="visibility: hidden; position:absolute; left:-1000px">';
+                'aria-invalid="false">';
         },
 
         /**
@@ -472,22 +548,39 @@ define([
          * @private
          */
         _EventListener: function () {
+            var $widget = this,
+                options = this.options.classes,
+                target;
 
-            var $widget = this;
-
-            $widget.element.on('click', '.' + this.options.classes.optionClass, function () {
+            $widget.element.on('click', '.' + options.optionClass, function () {
                 return $widget._OnClick($(this), $widget);
             });
 
-            $widget.element.on('change', '.' + this.options.classes.selectClass, function () {
+            $widget.element.on('change', '.' + options.selectClass, function () {
                 return $widget._OnChange($(this), $widget);
             });
 
-            $widget.element.on('click', '.' + this.options.classes.moreButton, function (e) {
+            $widget.element.on('click', '.' + options.moreButton, function (e) {
                 e.preventDefault();
 
                 return $widget._OnMoreClick($(this));
             });
+
+            $widget.element.on('keydown', function (e) {
+                if (e.which === 13) {
+                    target = $(e.target);
+
+                    if (target.is('.' + options.optionClass)) {
+                        return $widget._OnClick(target, $widget);
+                    } else if (target.is('.' + options.selectClass)) {
+                        return $widget._OnChange(target, $widget);
+                    } else if (target.is('.' + options.moreButton)) {
+                        e.preventDefault();
+
+                        return $widget._OnMoreClick(target);
+                    }
+                }
+            });
         },
 
         /**
@@ -498,13 +591,17 @@ define([
          * @private
          */
         _OnClick: function ($this, $widget) {
-
             var $parent = $this.parents('.' + $widget.options.classes.attributeClass),
+                $wrapper = $this.parents('.' + $widget.options.classes.attributeOptionsWrapper),
                 $label = $parent.find('.' + $widget.options.classes.attributeSelectedOptionLabelClass),
                 attributeId = $parent.attr('attribute-id'),
+                $input = $parent.find('.' + $widget.options.classes.attributeInput);
+
+            if ($widget.inProductList) {
                 $input = $widget.productForm.find(
                     '.' + $widget.options.classes.attributeInput + '[name="super_attribute[' + attributeId + ']"]'
                 );
+            }
 
             if ($this.hasClass('disabled')) {
                 return;
@@ -514,11 +611,13 @@ define([
                 $parent.removeAttr('option-selected').find('.selected').removeClass('selected');
                 $input.val('');
                 $label.text('');
+                $this.attr('aria-checked', false);
             } else {
                 $parent.attr('option-selected', $this.attr('option-id')).find('.selected').removeClass('selected');
                 $label.text($this.attr('option-label'));
                 $input.val($this.attr('option-id'));
                 $this.addClass('selected');
+                $widget._toggleCheckedAttributes($this, $wrapper);
             }
 
             $widget._Rebuild();
@@ -533,6 +632,19 @@ define([
             $input.trigger('change');
         },
 
+        /**
+         * Toggle accessibility attributes
+         *
+         * @param {Object} $this
+         * @param {Object} $wrapper
+         * @private
+         */
+        _toggleCheckedAttributes: function ($this, $wrapper) {
+            $wrapper.attr('aria-activedescendant', $this.attr('id'))
+                    .find('.' + this.options.classes.optionClass).attr('aria-checked', false);
+            $this.attr('aria-checked', true);
+        },
+
         /**
          * Event for select
          *
@@ -543,9 +655,13 @@ define([
         _OnChange: function ($this, $widget) {
             var $parent = $this.parents('.' + $widget.options.classes.attributeClass),
                 attributeId = $parent.attr('attribute-id'),
+                $input = $parent.find('.' + $widget.options.classes.attributeInput);
+
+            if ($widget.productForm.length > 0) {
                 $input = $widget.productForm.find(
                     '.' + $widget.options.classes.attributeInput + '[name="super_attribute[' + attributeId + ']"]'
                 );
+            }
 
             if ($this.val() > 0) {
                 $parent.attr('option-selected', $this.val());
@@ -687,7 +803,6 @@ define([
                     'prices': $widget._getPrices(result, $productPrice.priceBox('option').prices)
                 }
             );
-
         },
 
         /**
diff --git a/app/code/Magento/Theme/view/frontend/templates/messages.phtml b/app/code/Magento/Theme/view/frontend/templates/messages.phtml
index 2bd2357a27e1adbf79a9dbf7c34cc9c6d2d0da29..7ef534ab5d65c5b1df7014509e4ab7b3b41c3884 100644
--- a/app/code/Magento/Theme/view/frontend/templates/messages.phtml
+++ b/app/code/Magento/Theme/view/frontend/templates/messages.phtml
@@ -5,7 +5,8 @@
  */
 ?>
 <div data-bind="scope: 'messages'">
-    <div data-bind="foreach: { data: cookieMessages, as: 'message' }" class="messages">
+    <!-- ko if: cookieMessages && cookieMessages.length > 0 -->
+    <div role="alert" data-bind="foreach: { data: cookieMessages, as: 'message' }" class="messages">
         <div data-bind="attr: {
             class: 'message-' + message.type + ' ' + message.type + ' message',
             'data-ui-id': 'message-' + message.type
@@ -13,7 +14,9 @@
             <div data-bind="html: message.text"></div>
         </div>
     </div>
-    <div data-bind="foreach: { data: messages().messages, as: 'message' }" class="messages">
+    <!-- /ko -->
+    <!-- ko if: messages().messages && messages().messages.length > 0 -->
+    <div role="alert" data-bind="foreach: { data: messages().messages, as: 'message' }" class="messages">
         <div data-bind="attr: {
             class: 'message-' + message.type + ' ' + message.type + ' message',
             'data-ui-id': 'message-' + message.type
@@ -21,6 +24,7 @@
             <div data-bind="html: message.text"></div>
         </div>
     </div>
+    <!-- /ko -->
 </div>
 <script type="text/x-magento-init">
     {
diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js b/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js
index 4596569a7b6ac4a98bd727e8a7c1ad273a8889cc..151219b87abc5d540a35cc4e0198688bb6f6aca8 100755
--- a/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js
@@ -24,7 +24,7 @@ define([
             tooltipTpl: 'ui/form/element/helper/tooltip',
             fallbackResetTpl: 'ui/form/element/helper/fallback-reset',
             'input_type': 'input',
-            placeholder: '',
+            placeholder: false,
             description: '',
             labelVisible: true,
             label: '',
@@ -74,6 +74,15 @@ define([
             return this;
         },
 
+        /**
+         * Checks if component has error.
+         *
+         * @returns {Object}
+         */
+        checkInvalid: function () {
+            return this.error() && this.error().length ? this : null;
+        },
+
         /**
          * Initializes observable properties of instance
          *
@@ -84,7 +93,7 @@ define([
 
             this._super();
 
-            this.observe('error disabled focused preview visible value warn isDifferedFromDefault')
+            this.observe('error disabled focused preview visible value warn notice isDifferedFromDefault')
                 .observe('isUseDefault')
                 .observe({
                     'required': !!rules['required-entry']
@@ -114,6 +123,7 @@ define([
             _.extend(this, {
                 uid: uid,
                 noticeId: 'notice-' + uid,
+                errorId: 'error-' + uid,
                 inputName: utils.serializeName(name.join('.')),
                 valueUpdate: valueUpdate
             });
@@ -440,6 +450,23 @@ define([
          */
         userChanges: function () {
             this.valueChangedByUser = true;
+        },
+
+        /**
+         * Returns correct id for 'aria-describedby' accessibility attribute
+         *
+         * @returns {Boolean|String}
+         */
+        getDescriptionId: function () {
+            var id = false;
+
+            if (this.error()) {
+                id = this.errorId;
+            } else if (this.notice()) {
+                id = this.noticeId;
+            }
+
+            return id;
         }
     });
 });
diff --git a/app/code/Magento/Ui/view/base/web/js/form/form.js b/app/code/Magento/Ui/view/base/web/js/form/form.js
index 672af8a7ed845a12efe19f0801325e23c06932a6..0a3d6ee5850c425b861cb084761cde1cf49df2fb 100644
--- a/app/code/Magento/Ui/view/base/web/js/form/form.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/form.js
@@ -248,19 +248,31 @@ define([
          * @param {Object} data
          */
         save: function (redirect, data) {
-            var scrollTop;
-
             this.validate();
 
             if (!this.additionalInvalid && !this.source.get('params.invalid')) {
                 this.setAdditionalData(data)
                     .submit(redirect);
             } else {
-                scrollTop = $(this.errorClass).offset().top - window.innerHeight / 2;
-                window.scrollTo(0, scrollTop);
+                this.focusInvalid();
             }
         },
 
+        /**
+         * Tries to set focus on first invalid form field.
+         *
+         * @returns {Object}
+         */
+        focusInvalid: function () {
+            var invalidField = _.find(this.delegate('checkInvalid'));
+
+            if (!_.isUndefined(invalidField) && _.isFunction(invalidField.focused)) {
+                invalidField.focused(true);
+            }
+
+            return this;
+        },
+
         /**
          * Set additional data to source before form submit and after validation.
          *
diff --git a/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js b/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js
index b39fb1a371801f7f4a06c786341943ee0d245c41..0fc16010af36321c382d906a3312207d53d5e135 100644
--- a/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js
+++ b/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js
@@ -486,7 +486,7 @@ define([
         },
 
         /**
-         * Counts number of invalid fields accros all active records.
+         * Counts number of invalid fields across all active records.
          *
          * @returns {Number}
          */
@@ -502,6 +502,16 @@ define([
             return errorsCount;
         },
 
+        /**
+         * Translatable error message text.
+         *
+         * @returns {String}
+         */
+        countErrorsMessage: function () {
+            return $t('There are {placeholder} messages requires your attention.')
+                .replace('{placeholder}', this.countErrors());
+        },
+
         /**
          * Checks if editor has any errors.
          *
diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js
index 764b11dfbfea202bf40c8e631fdbcfdc0da1285e..7823871fd694c362912e0479a408d7befcf25450 100644
--- a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js
+++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js
@@ -27,12 +27,16 @@ define([
                 message: ''
             };
 
+        if (_.isObject(params)) {
+            message = params.message || '';
+        }
+
         if (!rulesList[id]) {
             return result;
         }
 
         rule    = rulesList[id];
-        message = rule.message;
+        message = message || rule.message;
         valid   = rule.handler(value, params, additionalParams);
 
         if (!valid) {
@@ -68,7 +72,7 @@ define([
             };
 
             _.every(rules, function (ruleParams, id) {
-                if (ruleParams !== false || additionalParams) {
+                if (ruleParams.validate || ruleParams !== false || additionalParams) {
                     result = validate(id, value, ruleParams, additionalParams);
 
                     return result.passed;
diff --git a/app/code/Magento/Ui/view/base/web/templates/form/field.html b/app/code/Magento/Ui/view/base/web/templates/form/field.html
index adc9814a91d0ee3cbe5a1c955b532d385536fd8f..4a9d8f8c75a77d266297dfec620d548a7a12ee76 100644
--- a/app/code/Magento/Ui/view/base/web/templates/form/field.html
+++ b/app/code/Magento/Ui/view/base/web/templates/form/field.html
@@ -40,4 +40,4 @@
 
         <render args="$data.service.template" if="$data.hasService()"/>
     </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/editing/header-buttons.html b/app/code/Magento/Ui/view/base/web/templates/grid/editing/header-buttons.html
index ab0131ed667cfefb4074e47c5aa69727e039d16f..fd4e61108aae0ef8e34e6a713207cb94a4560049 100644
--- a/app/code/Magento/Ui/view/base/web/templates/grid/editing/header-buttons.html
+++ b/app/code/Magento/Ui/view/base/web/templates/grid/editing/header-buttons.html
@@ -4,19 +4,21 @@
  * See COPYING.txt for license details.
  */
 -->
-<div class="data-grid-info-panel" visible="isMultiEditing || (hasActive() && (hasMessages() || hasErrors() ))">
-    <div class="messages" visible="hasMessages() || hasErrors()">
-        <div class="message message-warning" visible="hasErrors()">
-            <strong>There are <text args="countErrors()"/> messages requires your attention.</strong>
-            Please make corrections to the errors in the table below and re-submit.
+<div if="isMultiEditing || (hasActive() && (hasMessages() || hasErrors() ))"
+     attr="role: (isMultiEditing && multiEditingButtons) ? 'alertdialog' : 'alert'"
+     class="data-grid-info-panel">
+    <div if="hasMessages() || hasErrors()" class="messages">
+        <div if="hasErrors()" class="message message-warning">
+            <strong><text args="countErrorsMessage()"/></strong>
+            <span translate="'Please make corrections to the errors in the table below and re-submit.'"/>
         </div>
         <div class="message" outereach="messages" text="message"
-            css="
-                'message-warning': type === 'warning',
-                'message-error': type === 'error',
-                'message-success': type === 'success'"/>
+             css="
+                 'message-warning': type === 'warning',
+                 'message-error': type === 'error',
+                 'message-success': type === 'success'"/>
     </div>
-    <div class="data-grid-info-panel-actions" visible="isMultiEditing && multiEditingButtons">
+    <div if="isMultiEditing && multiEditingButtons" class="data-grid-info-panel-actions">
         <button class="action-tertiary" type="button" click="cancel">
             <span translate="'Cancel'"/>
         </button>
diff --git a/app/code/Magento/Ui/view/frontend/web/template/messages.html b/app/code/Magento/Ui/view/frontend/web/template/messages.html
index 5966523ec89206d829f2da18784ea756d83e9492..f0f03ec2e53257d2965a3acbed96d95faa1ba860 100644
--- a/app/code/Magento/Ui/view/frontend/web/template/messages.html
+++ b/app/code/Magento/Ui/view/frontend/web/template/messages.html
@@ -6,13 +6,13 @@
 -->
 <div data-role="checkout-messages" class="messages" data-bind="visible: isVisible(), click: removeAll">
     <!-- ko foreach: messageContainer.getErrorMessages() -->
-    <div class="message message-error error">
+    <div role="alert" class="message message-error error">
         <div data-ui-id="checkout-cart-validationmessages-message-error" data-bind="text: $data"></div>
     </div>
     <!--/ko-->
     <!-- ko foreach: messageContainer.getSuccessMessages() -->
-    <div class="message message-success success">
+    <div role="alert" class="message message-success success">
         <div data-ui-id="checkout-cart-validationmessages-message-success" data-bind="text: $data"></div>
     </div>
     <!--/ko-->
-</div>
\ No newline at end of file
+</div>
diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/checkbox.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/checkbox.html
index 50a27e6902528938e66ab2e632aa98ef23993701..c22df869a0013a9dd8960cada1ab7fb1b2601587 100644
--- a/app/code/Magento/Ui/view/frontend/web/templates/form/element/checkbox.html
+++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/checkbox.html
@@ -5,7 +5,19 @@
  */
 -->
 <div class="choice field">
-    <input type="checkbox" class="checkbox" data-bind="checked: value, attr: { id: uid, disabled: disabled, name: inputName }, hasFocus: focused">
+    <input type="checkbox"
+           class="checkbox"
+           data-bind="
+           checked: value,
+           attr: {
+                id: uid,
+                disabled: disabled,
+                name: inputName,
+                'aria-describedby': getDescriptionId(),
+                'aria-required': required,
+                'aria-invalid': error() ? true : 'false'
+                },
+           hasFocus: focused">
 
     <label class="label" data-bind="checked: value, attr: { for: uid }">
         <span data-bind="text: description || label"></span>
diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/date.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/date.html
index be2034a6a0b9710b0bc044a2334bf0744c5c1781..48faf4b73a5f5e4a4987bbc9c4b33595e74bed3c 100644
--- a/app/code/Magento/Ui/view/frontend/web/templates/form/element/date.html
+++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/date.html
@@ -11,6 +11,8 @@
         value: value,
         name: inputName,
         placeholder: placeholder,
-        'aria-describedby': noticeId,
+        'aria-describedby': getDescriptionId(),
+        'aria-required': required,
+        'aria-invalid': error() ? true : 'false',
         disabled: disabled
     }" />
diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/email.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/email.html
index dfd75630e08bc086d7cf87dedf55000cb9ffc10c..57f94670323ad25c2cd5b8038eee5a7d4f984c3f 100644
--- a/app/code/Magento/Ui/view/frontend/web/templates/form/element/email.html
+++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/email.html
@@ -10,7 +10,9 @@
     attr: {
         name: inputName,
         placeholder: placeholder,
-        'aria-describedby': noticeId,
+        'aria-describedby': getDescriptionId(),
+        'aria-required': required,
+        'aria-invalid': error() ? true : 'false',
         id: uid,
         disabled: disabled
     }"/>
diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/input.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/input.html
index 01234333d5816573d1c2ae291446229e8a317e9b..a09e0383ef81d65103bbc3c53182c6740b7d3587 100644
--- a/app/code/Magento/Ui/view/frontend/web/templates/form/element/input.html
+++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/input.html
@@ -11,7 +11,9 @@
     attr: {
         name: inputName,
         placeholder: placeholder,
-        'aria-describedby': noticeId,
+        'aria-describedby': getDescriptionId(),
+        'aria-required': required,
+        'aria-invalid': error() ? true : 'false',
         id: uid,
         disabled: disabled
     }" />
diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/password.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/password.html
index 825a9869ec2efbf9be3ad4492b972cb30946ec9f..0d93d48aa96f6d7d10a7b02160aded2a489cf442 100644
--- a/app/code/Magento/Ui/view/frontend/web/templates/form/element/password.html
+++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/password.html
@@ -10,7 +10,9 @@
     attr: {
         name: inputName,
         placeholder: placeholder,
-        'aria-describedby': noticeId,
+        'aria-describedby': getDescriptionId(),
+        'aria-required': required,
+        'aria-invalid': error() ? true : 'false',
         id: uid,
         disabled: disabled
     }"/>
diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/select.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/select.html
index edd1395c5a719a0050b93afdcc16628b3df40f97..37d87d183e023574f4a1485649fd5046aa43f8fd 100644
--- a/app/code/Magento/Ui/view/frontend/web/templates/form/element/select.html
+++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/select.html
@@ -9,7 +9,9 @@
         name: inputName,
         id: uid,
         disabled: disabled,
-        'aria-describedby': noticeId,
+        'aria-describedby': getDescriptionId(),
+        'aria-required': required,
+        'aria-invalid': error() ? true : 'false',
         placeholder: placeholder
     },
     hasFocus: focused,
diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/field.html b/app/code/Magento/Ui/view/frontend/web/templates/form/field.html
index f3be62f116b8358e9424934951dc9c40a4a2d22a..d0bc45c74dbb41173454e0fff3f315b06841505c 100644
--- a/app/code/Magento/Ui/view/frontend/web/templates/form/field.html
+++ b/app/code/Magento/Ui/view/frontend/web/templates/form/field.html
@@ -36,16 +36,22 @@
         <!-- /ko -->
 
         <!-- ko if: element.notice -->
-            <div class="field-note" data-bind="attr: { id: element.noticeId }"><span data-bind="text: element.notice"></span></div>
+            <div class="field-note" data-bind="attr: { id: element.noticeId }">
+                <span data-bind="text: element.notice"/>
+            </div>
         <!-- /ko -->
 
         <!-- ko if: element.error() -->
-            <div class="mage-error" data-bind="attr: { for: element.uid }, text: element.error" generated="true"></div>
+            <div class="field-error" data-bind="attr: { id: element.errorId }" generated="true">
+                <span data-bind="text: element.error"/>
+            </div>
         <!-- /ko -->
 
         <!-- ko if: element.warn() -->
-            <div class="message warning" generated="true"><span data-bind="text: element.warn"></span></div>
+            <div role="alert" class="message warning" data-bind="attr: { id: element.warningId }" generated="true">
+                <span data-bind="text: element.warn"/>
+            </div>
         <!-- /ko -->
     </div>
 </div>
-<!-- /ko -->
\ No newline at end of file
+<!-- /ko -->
diff --git a/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less
index 585269e14d685dd8bc3e2f4ebcd90e1b500881da..bdb94e3a8202f9dc0142d5e81c5d6941e1567835 100644
--- a/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less
+++ b/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less
@@ -8,7 +8,16 @@
 //  _____________________________________________
 
 @message-system__background-color: @color-lazy-sun;
-@message-system-short-message__padding-vertical: 1.8rem;
+@message-system__border-color: @color-gray82;
+@message-system__border-width: .1rem;
+@message-system__color: @color-gray20;
+@message-system-short__padding-vertical: 1.5rem;
+@message-system-short-wrapper__height: 5rem;
+
+//  Triangle marker
+@message-system-triangle__height: .5rem;
+@message-system-triangle__padding-right: 3rem;
+@message-system-triangle__width: .8rem;
 
 //
 //  Message system
@@ -17,34 +26,124 @@
 .message-system-inner {
     &:extend(.abs-clearfix all);
     background: @message-system__background-color;
+    border: solid @message-system__border-color;
+    border-width: 0 @message-system__border-width @message-system__border-width;
+    position: relative;
+
+    .message-error {
+        background: none;
+    }
+
+    .message {
+        background: none;
+        margin: 0 0 -3px;
+        overflow: hidden;
+        padding: @message-system-short__padding-vertical 0 @message-system-short__padding-vertical 3.3rem;
+
+        &:before {
+            left: .3rem;
+        }
+    }
 
-    .message-system-list {
-        float: left;
-        width: 75%;
+    .action-menu-item {
+        &.action-close-wrapper {
+            width: 3.5rem;
+        }
+
+        .action-close {
+            float: right;
+        }
+
+        float: right;
+        padding: @message-system-short__padding-vertical 0 0;
+        vertical-align: top;
     }
 }
 
 .message-system-list {
+    border-bottom: 1px solid @message-system__border-color;
+    border-top: 1px solid @message-system__border-color;
     list-style: none;
-    margin: 0;
-    padding: 0;
+    margin: 0 0 1.5rem;
+
+    li {
+        + li {
+            border-top: 1px dashed @message-system__border-color;
+        }
+    }
 }
 
 .message-system-short {
+    min-height: @message-system-short-wrapper__height;
+
+    .action-close-wrapper {
+        display: none;
+    }
+}
+
+.message-system-short-wrapper {
     overflow: hidden;
-    text-align: right;
+    padding: 0 1.5rem 0 @indent__l;
+}
 
-    .message-system-short-label {
-        display: inline-block;
-        padding: @message-system-short-message__padding-vertical .3rem @message-system-short-message__padding-vertical 1rem;
+.message-system-collapsible {
+    background: @message-system__background-color;
+    border: @message-system__border-width solid @message-system__border-color;
+    border-top: 0;
+    display: none;
+    left: -1px;
+    padding: 0 @indent__l @message-system-short__padding-vertical;
+    position: absolute;
+    right: -1px;
+    top: 100%;
+    z-index: @z-index-5 - 2;
+
+    ._active & {
+        display: block;
     }
+}
 
-    .message {
+.message-system-action-dropdown {
+    .lib-button-reset();
+    float: right;
+    margin: @message-system-short__padding-vertical 0;
+    position: relative;
+
+    .action-toggle-triangle (
+        @_dropdown__padding-right: @message-system-triangle__padding-right;
+        @_triangle__height: @message-system-triangle__height;
+        @_triangle__width: @message-system-triangle__width;
+        @_triangle__color: @message-system__color;
+        @_triangle__hover__color: darken(@message-system__color, 10%);
+        @_triangle__right: (@message-system-triangle__padding-right / 2) - (@message-system-triangle__width/ 2);
+    );
+}
+
+.message-system-summary {
+    text-align: right;
+
+    .action__message-log {
+        border-right: 1px solid @message-system__border-color;
         display: inline-block;
-        padding: @message-system-short-message__padding-vertical 2rem @message-system-short-message__padding-vertical 3.3rem;
+        margin: 0 .2rem 0 @indent__xs;
+        padding-right: @indent__xs;
 
-        &:before {
-            left: .3rem;
+        &:last-child {
+            border-right: 0;
+            margin: 0;
+            padding: 0;
         }
     }
 }
+
+.notices-wrapper {
+    .admin__data-grid-loading-mask {
+        display: none;
+        min-height: @message-system-short-wrapper__height + @message-system__border-width;
+        z-index: @z-index-5 - 1;
+    }
+
+    .admin__data-grid-outer-wrap {
+        min-height: 0;
+    }
+}
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less
index 9d9a4cab990477f594601edefce714b2e1ad90ef..e955affe3fb4394a9daa38eeba643b64d8c1e658 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less
@@ -28,6 +28,9 @@
 @alert-icon__warning__content: @icon-warning__content;
 @alert-icon__warning__color: @color-phoenix;
 
+@alert-icon__progress__content: @icon-refresh__content;
+@alert-icon__progress__color: @color-blue-dodger;
+
 @alert-icon__success__content: @icon-check-mage__content;
 @alert-icon__success__color: @color-green-apple;
 
@@ -93,6 +96,13 @@
     }
 }
 
+.message-progress {
+    &:before {
+        color: @alert-icon__progress__color;
+        content: @alert-icon__progress__content;
+    }
+}
+
 .message-error {
     background: @alert__error__background-color;
 
@@ -128,4 +138,4 @@
 .message-in-rating-edit {
     margin-left: 1.8rem;
     margin-right: 1.8rem;
-}
+}
\ No newline at end of file
diff --git a/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less
index 3b8e84da841069ed41c651329f48ef91a0e10133..7fb7985ada12a8f932134c8afeee765a260f6083 100644
--- a/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less
+++ b/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less
@@ -112,6 +112,11 @@
         }
     }
 
+    .action-skip-wrapper {
+        height: 0;
+        position: relative;
+    }
+
     //
     //  Global notice
     //  ---------------------------------------------
diff --git a/app/design/frontend/Magento/blank/web/css/source/_forms.less b/app/design/frontend/Magento/blank/web/css/source/_forms.less
index 72e014b8305aaf86e0405df59654c8faf1297834..8c7258c390ab723e4f55492f42cffccfc8f60a09 100644
--- a/app/design/frontend/Magento/blank/web/css/source/_forms.less
+++ b/app/design/frontend/Magento/blank/web/css/source/_forms.less
@@ -92,10 +92,15 @@
         }
     }
 
+    .field-error,
     div.mage-error[generated] {
         margin-top: 7px;
     }
 
+    .field-error {
+        .lib-form-validation-note();
+    }
+
     .field .tooltip {
         .lib-tooltip(right);
         .tooltip-content {
diff --git a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less
index 5581eb7fb05212c4c24967baf6aab30398bd8072..12ef890f4305539539582af433c1b7961a15043a 100644
--- a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less
@@ -168,6 +168,11 @@
         }
     }
 
+    .action-skip-wrapper {
+        height: 0;
+        position: relative;
+    }
+
     //
     //  Global notice
     //  ---------------------------------------------
diff --git a/app/design/frontend/Magento/luma/web/css/source/_forms.less b/app/design/frontend/Magento/luma/web/css/source/_forms.less
index 8d22eba9ca51f892425a60b73a43cedc6a149fa1..d29e53d7fefe68622af55b4004c3742df2be93d5 100644
--- a/app/design/frontend/Magento/luma/web/css/source/_forms.less
+++ b/app/design/frontend/Magento/luma/web/css/source/_forms.less
@@ -113,10 +113,15 @@
         .select-styling();
     }
 
+    .field-error,
     div.mage-error[generated] {
         margin-top: 7px;
     }
 
+    .field-error {
+        .lib-form-validation-note();
+    }
+
     //  TEMP
 
     .field .tooltip {
diff --git a/composer.json b/composer.json
index 8053b76a01a2283f5f1ad39ec8028123bca9bb14..a0813c8462066d0eabcacb4a21cdb7b5a09dbd13 100644
--- a/composer.json
+++ b/composer.json
@@ -64,6 +64,7 @@
         "ext-mbstring": "*",
         "ext-openssl": "*",
         "ext-zip": "*",
+        "ext-pdo_mysql": "*",
         "sjparkinson/static-review": "~4.1",
         "ramsey/uuid": "3.4"
     },
diff --git a/composer.lock b/composer.lock
index 6fd73ef405c822bcb049709ec1150c6e51254e35..4d1f23dad9b512b00d28341d28d1db5ac05899bb 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,8 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "ad3de2234f78fd4b353ae6a1b22401fc",
-    "content-hash": "25dcf96ed1d8b12a25111e8b3af61317",
+    "hash": "c8f9e4332a46ace884514d9843021898",
+    "content-hash": "fa9bd2b88f3c2e1bf562c439cecca917",
     "packages": [
         {
             "name": "braintree/braintree_php",
@@ -4597,7 +4597,8 @@
         "ext-xsl": "*",
         "ext-mbstring": "*",
         "ext-openssl": "*",
-        "ext-zip": "*"
+        "ext-zip": "*",
+        "ext-pdo_mysql": "*"
     },
     "platform-dev": []
 }
diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml
index 20359c41bd55fb148c4dd407a0b75c4e2cda8f93..bd2a0e60b20ab032b034a2930f1bd1e24f54f7fc 100644
--- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml
+++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml
@@ -7,6 +7,6 @@
  -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd">
     <page name="BraintreeSettlementReportIndex" area="Adminhtml" mca="braintree/report/index" module="Magento_Braintree">
-        <block name="settlementReportGrid" class="Magento\Braintree\Test\Block\Adminhtml\Report\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/>
+        <block name="settlementReportGrid" class="Magento\Braintree\Test\Block\Adminhtml\Report\Grid" locator="//div[contains(@data-bind, 'braintree_report')]" strategy="xpath"/>
     </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php
index 9b57ce34f4eea73928920c87dfdd1ebab23c81d1..3134fda0d2b2b3f98b3e9d0ef5c835e8cb1aad53 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php
@@ -567,12 +567,19 @@ class View extends AbstractConfigureBlock
      */
     public function closeFullImage()
     {
-        $element = $this->browser->find($this->fullImageClose, Locator::SELECTOR_CSS);
-        if (!$element->isVisible()) {
-            $element->hover();
-            $this->waitForElementVisible($this->fullImageClose);
-        }
-        $element->click();
+        $this->_rootElement->waitUntil(
+            function () {
+                $this->browser->find($this->fullImage)->hover();
+
+                if ($this->browser->find($this->fullImageClose)->isVisible()) {
+                    $this->browser->find($this->fullImageClose)->click();
+
+                    return true;
+                }
+
+                return null;
+            }
+        );
     }
 
     /**
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml
index a1de0d5599564a5693604efea577ae2a14e0d76e..c6a125bfd4ed236e4c8a6db38f6bfd7fb11a3e85 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml
@@ -7,7 +7,7 @@
  -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd">
     <page name="CatalogProductIndex" area="Adminhtml" mca="catalog/product/index" module="Magento_Catalog">
-        <block name="productGrid" class="Magento\Catalog\Test\Block\Adminhtml\Product\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/>
+        <block name="productGrid" class="Magento\Catalog\Test\Block\Adminhtml\Product\Grid" locator="//div[contains(@data-bind, 'product_listing')]" strategy="xpath"/>
         <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/>
         <block name="gridPageActionBlock" class="Magento\Catalog\Test\Block\Adminhtml\Product\GridPageAction" locator="#add_new_product" strategy="css selector"/>
     </page>
diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml
index e23472508e83a83ca56d6e738bed43f3ca65bd88..c369a950df315087b09ff9528aa9c9227d258d18 100644
--- a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml
@@ -9,6 +9,6 @@
     <page name="CmsBlockIndex" area="Adminhtml" mca="cms/block/index" module="Magento_Cms">
         <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages .message" strategy="css selector" />
         <block name="gridPageActions" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector" />
-        <block name="cmsBlockGrid" class="Magento\Cms\Test\Block\Adminhtml\Block\CmsGrid" locator=".admin__data-grid-outer-wrap" strategy="css selector" />
+        <block name="cmsBlockGrid" class="Magento\Cms\Test\Block\Adminhtml\Block\CmsGrid" locator="//div[contains(@data-bind, 'cms_block_listing')]" strategy="xpath" />
     </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml
index ed2037cf893a39a14d6eee047f14c69da04f3dea..467d8f8a2ffe8309ef7964d4cdf925ab3544a347 100644
--- a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml
@@ -8,7 +8,7 @@
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd">
     <page name="CmsPageIndex" area="Adminhtml" mca="cms/page/index" module="Magento_Cms">
         <block name="pageActionsBlock" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector" />
-        <block name="cmsPageGridBlock" class="Magento\Cms\Test\Block\Adminhtml\Page\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector" />
+        <block name="cmsPageGridBlock" class="Magento\Cms\Test\Block\Adminhtml\Page\Grid" locator="//div[contains(@data-bind, 'cms_page_listing')]" strategy="xpath" />
         <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages .message" strategy="css selector" />
     </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml
index d0247118d2d3b4261da1ba0e288f270f7ebd8ab3..7b908210b6e7f58de28dcf8188da38748b9a6e05 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml
@@ -9,6 +9,6 @@
   <page name="CustomerIndex" area="Adminhtml" mca="customer/index" module="Magento_Customer">
     <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/>
     <block name="pageActionsBlock" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector"/>
-    <block name="customerGridBlock" class="Magento\Customer\Test\Block\Adminhtml\CustomerGrid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/>
+    <block name="customerGridBlock" class="Magento\Customer\Test\Block\Adminhtml\CustomerGrid" locator="//div[contains(@data-bind, 'customer_listing')]" strategy="xpath"/>
   </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml
index 1382f1c2567ddc1aac85673369703e632676bd11..beee22011c69e80d0ab7d20516a2193518ae0371 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml
@@ -8,7 +8,7 @@
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd">
     <page name="OrderIndex" area="Adminhtml" mca="sales/order/index" module="Magento_Sales">
         <block name="gridPageActions" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector"/>
-        <block name="salesOrderGrid" class="Magento\Sales\Test\Block\Adminhtml\Order\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/>
+        <block name="salesOrderGrid" class="Magento\Sales\Test\Block\Adminhtml\Order\Grid" locator="//div[contains(@data-bind, 'sales_order_grid')]" strategy="xpath"/>
         <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/>
     </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml b/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml
index 85cc69ea56584e326ffabd656e694b11a1127681..a979454142f42adf17f8a566de68efd80b083bd6 100644
--- a/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml
@@ -9,6 +9,6 @@
     <page name="synonymGroupIndex" area="Adminhtml" mca="search/synonyms/index" module="Magento_Search">
         <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages .message" strategy="css selector" />
         <block name="gridPageActions" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector" />
-        <block name="synonymGroupGrid" class="Magento\Search\Test\Block\Adminhtml\Block\SynonymGroupGrid" locator=".admin__data-grid-outer-wrap" strategy="css selector" />
+        <block name="synonymGroupGrid" class="Magento\Search\Test\Block\Adminhtml\Block\SynonymGroupGrid" locator="//div[contains(@data-bind, 'search_synonyms_grid')]" strategy="xpath" />
     </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml b/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml
index 81667dbbcc54dd5bf66c0c600a615d5709a01759..e9d5e1d21175ddb56d016c12af6c89e6c9fd20d0 100644
--- a/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml
@@ -7,7 +7,7 @@
  -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd">
   <page name="ShipmentIndex" area="Adminhtml" mca="sales/shipment" module="Magento_Shipping">
-    <block name="shipmentsGrid" class="Magento\Shipping\Test\Block\Adminhtml\Shipment\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/>
+    <block name="shipmentsGrid" class="Magento\Shipping\Test\Block\Adminhtml\Shipment\Grid" locator="//div[contains(@data-bind, 'sales_order_shipment_grid')]" strategy="xpath"/>
     <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/>
   </page>
 </config>
diff --git a/lib/web/mage/smart-keyboard-handler.js b/lib/web/mage/smart-keyboard-handler.js
index 859ab69372e09515f967a10a42c421ea7b750341..b6a65f2ee5d43b43ae91722b43507c39febd1941 100644
--- a/lib/web/mage/smart-keyboard-handler.js
+++ b/lib/web/mage/smart-keyboard-handler.js
@@ -16,7 +16,8 @@ define([
             CODE_TAB = 9;
 
         return {
-            apply: smartKeyboardFocus
+            apply: smartKeyboardFocus,
+            focus: handleFocus
         };
 
         /**
@@ -49,22 +50,39 @@ define([
          * Handle logic, when onTabKeyPress fired at first.
          * Then it changes state.
          */
-        function onFocusInHandler () {
+        function onFocusInHandler() {
             focusState = true;
-            $('body').addClass(tabFocusClass)
+            body.addClass(tabFocusClass)
                 .off('focusin.keyboardHandler', onFocusInHandler);
         }
 
         /**
          * Handle logic to remove state after onTabKeyPress to normal.
-         * @param {Event} event
          */
-        function onClickHandler(event) {
-            focusState  = false;
-            $('body').removeClass(tabFocusClass)
+        function onClickHandler() {
+            focusState = false;
+            body.removeClass(tabFocusClass)
                 .off('click', onClickHandler);
         }
+
+        /**
+         * Attach smart focus on specific element.
+         * @param {jQuery} element
+         */
+        function handleFocus(element) {
+            element.on('focusin.emulateTabFocus', function () {
+                focusState = true;
+                body.addClass(tabFocusClass);
+                element.off();
+            });
+
+            element.on('focusout.emulateTabFocus', function () {
+                focusState = false;
+                body.removeClass(tabFocusClass);
+                element.off();
+            });
+        }
     }
 
     return new KeyboardHandler;
-});
\ No newline at end of file
+});
diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js
index 6c8deba8164112a31939b11d1ed79b5c23dd7bc0..df2cd0288648e4a33b6e724c4141baf31d3a4d17 100644
--- a/lib/web/mage/validation.js
+++ b/lib/web/mage/validation.js
@@ -1543,6 +1543,7 @@
                 this.validate.resetForm();
             }
         },
+
         /**
          * Validation creation
          * @protected
@@ -1559,36 +1560,46 @@
 
             this._listenFormValidate();
         },
+
         /**
          * Validation listening
          * @protected
          */
         _listenFormValidate: function () {
-            $('form').on('invalid-form.validate', function (event, validation) {
-                var firstActive = $(validation.errorList[0].element || []),
-                    lastActive = $(validation.findLastActive() || validation.errorList.length && validation.errorList[0].element || []);
-
-                if (lastActive.is(':hidden')) {
-                    var parent = lastActive.parent();
-                    var windowHeight = $(window).height();
-                    $('html, body').animate({
-                        scrollTop: parent.offset().top - windowHeight / 2
-                    });
-                }
-
-                // ARIA (removing aria attributes if success)
-                var successList = validation.successList;
-                if (successList.length) {
-                    $.each(successList, function () {
-                        $(this)
-                            .removeAttr('aria-describedby')
-                            .removeAttr('aria-invalid');
-                    })
-                }
-                if (firstActive.length) {
-                    firstActive.focus();
-                }
-            });
+            $('form').on('invalid-form.validate', this.listenFormValidateHandler);
+        },
+
+        /**
+         * Handle form validation. Focus on first invalid form field.
+         *
+         * @param {jQuery.Event} event
+         * @param {Object} validation
+         */
+        listenFormValidateHandler:  function (event, validation) {
+            var firstActive = $(validation.errorList[0].element || []),
+                lastActive = $(validation.findLastActive() || validation.errorList.length && validation.errorList[0].element || []);
+
+            if (lastActive.is(':hidden')) {
+                var parent = lastActive.parent();
+                var windowHeight = $(window).height();
+                $('html, body').animate({
+                    scrollTop: parent.offset().top - windowHeight / 2
+                });
+            }
+
+            // ARIA (removing aria attributes if success)
+            var successList = validation.successList;
+            if (successList.length) {
+                $.each(successList, function () {
+                    $(this)
+                        .removeAttr('aria-describedby')
+                        .removeAttr('aria-invalid');
+                });
+            }
+
+            if (firstActive.length) {
+                firstActive.focus();
+            }
         }
     });
 
diff --git a/setup/src/Magento/Setup/Model/PhpReadinessCheck.php b/setup/src/Magento/Setup/Model/PhpReadinessCheck.php
index 06e8b34471dada4264aef7be5dcba41febc14dd8..74b2afd1419c440977f2b10418964286eb738b39 100644
--- a/setup/src/Magento/Setup/Model/PhpReadinessCheck.php
+++ b/setup/src/Magento/Setup/Model/PhpReadinessCheck.php
@@ -102,7 +102,8 @@ class PhpReadinessCheck
 
         $settings = array_merge(
             $this->checkXDebugNestedLevel(),
-            $this->checkPopulateRawPostSetting()
+            $this->checkPopulateRawPostSetting(),
+            $this->checkFunctionsExistence()
         );
 
         foreach ($settings as $setting) {
@@ -316,6 +317,33 @@ class PhpReadinessCheck
         return $data;
     }
 
+    /**
+     * Check whether all special functions exists
+     *
+     * @return array
+     */
+    private function checkFunctionsExistence()
+    {
+        $data = [];
+        $requiredFunctions = [
+            [
+                'name' => 'imagecreatefromjpeg',
+                'message' => 'You must have installed GD library with --with-jpeg-dir=DIR option.',
+                'helpUrl' => 'http://php.net/manual/en/image.installation.php',
+            ],
+        ];
+
+        foreach ($requiredFunctions as $function) {
+            $data['missed_function_' . $function['name']] = [
+                'message' => $function['message'],
+                'helpUrl' => $function['helpUrl'],
+                'error' => !function_exists($function['name']),
+            ];
+        }
+
+        return $data;
+    }
+
     /**
      * Normalize PHP Version
      *
diff --git a/setup/src/Magento/Setup/Test/Unit/Model/PhpReadinessCheckTest.php b/setup/src/Magento/Setup/Test/Unit/Model/PhpReadinessCheckTest.php
index c24e1792331ef09c20ac5549fc9d7edb84c8a3c1..a77bd46c79f25495570070968ae62ebb0762ef34 100644
--- a/setup/src/Magento/Setup/Test/Unit/Model/PhpReadinessCheckTest.php
+++ b/setup/src/Magento/Setup/Test/Unit/Model/PhpReadinessCheckTest.php
@@ -222,7 +222,12 @@ class PhpReadinessCheckTest extends \PHPUnit_Framework_TestCase
                     'message' => $xdebugMessage,
                     'error' => false,
                 ],
-            ]
+                'missed_function_imagecreatefromjpeg' => [
+                    'message' => 'You must have installed GD library with --with-jpeg-dir=DIR option.',
+                    'helpUrl' => 'http://php.net/manual/en/image.installation.php',
+                    'error' => false,
+                ],
+            ],
         ];
         if (!$this->isPhp7OrHhvm()) {
             $this->setUpNoPrettyVersionParser();
@@ -261,8 +266,13 @@ class PhpReadinessCheckTest extends \PHPUnit_Framework_TestCase
                 'xdebug_max_nesting_level' => [
                     'message' => $xdebugMessage,
                     'error' => true,
-                ]
-            ]
+                ],
+                'missed_function_imagecreatefromjpeg' => [
+                    'message' => 'You must have installed GD library with --with-jpeg-dir=DIR option.',
+                    'helpUrl' => 'http://php.net/manual/en/image.installation.php',
+                    'error' => false,
+                ],
+            ],
         ];
         if (!$this->isPhp7OrHhvm()) {
             $this->setUpNoPrettyVersionParser();
@@ -301,6 +311,13 @@ class PhpReadinessCheckTest extends \PHPUnit_Framework_TestCase
                 ]
             ];
         }
+
+        $expected['data']['missed_function_imagecreatefromjpeg'] = [
+            'message' => 'You must have installed GD library with --with-jpeg-dir=DIR option.',
+            'helpUrl' => 'http://php.net/manual/en/image.installation.php',
+            'error' => false,
+        ];
+
         $this->assertEquals($expected, $this->phpReadinessCheck->checkPhpSettings());
     }