Bug 1518487 - implement ColorContrast component to display a11y audit information for text color contrast. r=gl
authorYura Zenevich <yura.zenevich@gmail.com>
Tue, 12 Feb 2019 19:40:11 +0000
changeset 458788 b8b41d218866
parent 458787 8de6b5e7abb3
child 458789 090304ca37d9
push id35548
push useropoprus@mozilla.com
push dateWed, 13 Feb 2019 09:48:26 +0000
treeherdermozilla-central@93e37c529818 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1518487
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1518487 - implement ColorContrast component to display a11y audit information for text color contrast. r=gl MozReview-Commit-ID: DoOp2JhaQyD Differential Revision: https://phabricator.services.mozilla.com/D19053
devtools/client/accessibility/accessibility.css
devtools/client/accessibility/components/ColorContrastAccessibility.js
devtools/client/accessibility/constants.js
devtools/client/accessibility/test/mochitest/chrome.ini
devtools/client/accessibility/test/mochitest/contrast.snapshots.js
devtools/client/accessibility/test/mochitest/test_accessible_contrast.html
devtools/client/locales/en-US/accessibility.properties
--- a/devtools/client/accessibility/accessibility.css
+++ b/devtools/client/accessibility/accessibility.css
@@ -5,40 +5,41 @@
 :root {
   --accessibility-font-size: 12px;
   --accessibility-toolbar-height: 24px;
   --accessibility-toolbar-height-tall: 35px;
   --accessibility-toolbar-focus: var(--blue-50);
   --accessibility-toolbar-focus-alpha30: rgba(10, 132, 255, 0.3);
   --accessibility-full-length-minus-splitter: calc(100% - 1px);
   --accessibility-horizontal-padding: 5px;
+  --accessibility-horizontal-indent: 14px;
   --accessibility-properties-item-width: calc(100% - var(--accessibility-horizontal-padding));
   --accessibility-arrow-horizontal-padding: 4px;
   --accessibility-tree-row-height: 21px;
   --accessibility-unfocused-tree-focused-node-background: var(--grey-20);
   --accessibility-unfocused-tree-focused-node-twisty-fill: var(--theme-icon-dimmed-color);
   --accessibility-link-color: var(--blue-60);
   --accessibility-link-color-active: var(--blue-70);
   --accessible-role-active-background-color: var(--blue-50);
   --accessible-role-active-border-color: #FFFFFFB3;
-  --accessible-role-background-color: white;
-  --accessible-role-border-color: #CACAD1;
-  --accessible-role-color: var(--grey-60);
+  --accessible-label-background-color: white;
+  --accessible-label-border-color: #CACAD1;
+  --accessible-label-color: var(--grey-60);
 }
 
 :root.theme-dark {
   --accessibility-unfocused-tree-focused-node-background: var(--grey-70);
   --accessibility-unfocused-tree-focused-node-twisty-fill: var(--theme-selection-color);
   --accessibility-link-color: var(--theme-highlight-blue);
   --accessibility-link-color-active: var(--blue-40);
   --accessible-role-active-background-color: var(--blue-60);
   --accessible-role-active-border-color: #FFF6;
-  --accessible-role-background-color: var(--grey-80);
-  --accessible-role-border-color: var(--grey-50);
-  --accessible-role-color: var(--grey-40);
+  --accessible-label-background-color: var(--grey-80);
+  --accessible-label-border-color: var(--grey-50);
+  --accessible-label-color: var(--grey-40);
 }
 
 /* General */
 html,
 body {
   height: 100%;
   margin: 0;
   padding: 0;
@@ -407,19 +408,19 @@ body {
   padding-inline-end: var(--accessibility-arrow-horizontal-padding);
 }
 
 .accessible .tree .object-label {
   color: var(--theme-highlight-blue);
 }
 
 .accessible .tree .objectBox-accessible .accessible-role {
-  background-color: var(--accessible-role-background-color);
-  color: var(--accessible-role-color);
-  border: 1px solid var(--accessible-role-border-color);
+  background-color: var(--accessible-label-background-color);
+  color: var(--accessible-label-color);
+  border: 1px solid var(--accessible-label-border-color);
   border-radius: 3px;
   padding: 0px 2px;
   margin-inline-start: 5px;
 }
 
 .accessible .tree:focus .node.focused .objectBox-accessible .accessible-role {
   background-color: var(--accessible-role-active-background-color);
   border-color: var(--accessible-role-active-border-color);
@@ -471,8 +472,130 @@ body {
 /* Styling for accessible details panel when accessible is not available */
 .accessible .info {
   color: var(--theme-body-color);
   font-size: 110%;
   padding-inline-start: var(--accessibility-arrow-horizontal-padding);
   height: var(--accessibility-toolbar-height-tall);
   line-height: var(--accessibility-toolbar-height-tall);
 }
+
+/* Color Contrast */
+.accessibility-color-contrast-check,
+.accessibility-color-contrast {
+  position: relative;
+  display: flex;
+  cursor: default;
+  height: inherit;
+}
+
+.accessibility-color-contrast-check {
+  flex-direction: column;
+  padding: 4px var(--accessibility-horizontal-indent);
+  line-height: 20px;
+}
+
+.accessibility-color-contrast {
+  align-items: baseline;
+}
+
+.accessibility-color-contrast-header {
+  margin: 0;
+  font-weight: bold;
+  font-size: var(--accessibility-font-size);
+  line-height: var(--accessibility-toolbar-height);
+}
+
+.accessibility-color-contrast-annotation {
+  margin: 0;
+  white-space: normal;
+  color: var(--accessible-label-color);
+}
+
+.accessibility-color-contrast-annotation .link {
+  color: var(--accessibility-link-color);
+  cursor: pointer;
+  outline: 0;
+  white-space: nowrap;
+  font-style: normal;
+}
+
+.accessibility-color-contrast-annotation .link:hover:not(:focus) {
+  text-decoration: underline;
+}
+
+.accessibility-color-contrast-annotation .link:focus:not(:active) {
+  box-shadow: 0 0 0 2px var(--accessibility-toolbar-focus), 0 0 0 4px var(--accessibility-toolbar-focus-alpha30);
+  border-radius: 2px;
+}
+
+.accessibility-color-contrast-annotation .link:active {
+  color: var(--accessibility-link-color-active);
+  text-decoration: underline;
+}
+
+.accessibility-color-contrast-large-text {
+  background-color: var(--accessible-label-background-color);
+  color: var(--accessible-label-color);
+  outline: 1px solid var(--accessible-label-border-color);
+  -moz-outline-radius: 3px;
+  padding: 0px 2px;
+  margin-inline-start: 6px;
+  line-height: initial;
+}
+
+.accessibility-color-contrast .accessibility-contrast-value:not(:empty) {
+  margin-block-end: 4px;
+}
+
+.accessibility-color-contrast .accessibility-contrast-value:not(:empty):before {
+  content: "";
+  height: 14px;
+  width: 14px;
+  display: inline-flex;
+  background-color: var(--accessibility-contrast-color);
+  box-shadow: 0 0 0 1px var(--grey-40), 6px 5px var(--accessibility-contrast-bg), 6px 5px 0 1px var(--grey-40);
+  margin-inline-end: 11px;
+}
+
+.accessibility-color-contrast .accessibility-contrast-value:first-child:not(:empty):before {
+  margin-inline-start: 1px;
+}
+
+.accessibility-color-contrast .accessibility-contrast-value:not(:first-child):not(:empty):before {
+  margin-inline-start: 4px;
+}
+
+.accessibility-color-contrast .accessibility-contrast-value:not(:empty):after {
+  margin-inline-start: 4px;
+}
+
+.accessibility-color-contrast .accessibility-contrast-value:not(:empty).AA:after,
+.accessibility-color-contrast .accessibility-contrast-value:not(:empty).AAA:after {
+  color: var(--theme-highlight-green);
+}
+
+.accessibility-color-contrast .accessibility-contrast-value:not(:empty).fail:after {
+  color: #E57180;
+  content: "⚠️";
+}
+
+.accessibility-color-contrast .accessibility-contrast-value:not(:empty).AA:after {
+  content: "AA\2713";
+}
+
+.accessibility-color-contrast .accessibility-contrast-value:not(:empty).AAA:after {
+  content: "AAA\2713";
+}
+
+.accessibility-color-contrast .accessibility-color-contrast-label:after {
+  content: ":";
+}
+
+.accessibility-color-contrast .accessibility-color-contrast-label,
+.accessibility-color-contrast .accessibility-color-contrast-separator:before {
+  margin-inline-end: 3px;
+}
+
+.accessibility-color-contrast .accessibility-color-contrast-separator:before {
+  content: "-";
+  margin-inline-start: 4px;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/ColorContrastAccessibility.js
@@ -0,0 +1,219 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { div, span, h3 } = require("devtools/client/shared/vendor/react-dom-factories");
+const LearnMoreLink = createFactory(require("./LearnMoreLink"));
+
+const { A11Y_CONTRAST_LEARN_MORE_LINK } = require("../constants");
+const { L10N } = require("../utils/l10n");
+
+/**
+ * Component that renders a colour contrast value along with a swatch preview of what the
+ * text and background colours are.
+ */
+class ContrastValueClass extends Component {
+  static get propTypes() {
+    return {
+      backgroundColor: PropTypes.array.isRequired,
+      color: PropTypes.array.isRequired,
+      isLargeText: PropTypes.bool.isRequired,
+      value: PropTypes.number.isRequired,
+    };
+  }
+
+  render() {
+    const {
+      backgroundColor,
+      color,
+      isLargeText,
+      value,
+    } = this.props;
+
+    const className = [
+      "accessibility-contrast-value",
+      getContrastRatioScore(value, isLargeText),
+    ].join(" ");
+
+    return (
+      span({
+        className,
+        role: "presentation",
+        style: {
+          "--accessibility-contrast-color": `rgba(${color})`,
+          "--accessibility-contrast-bg": `rgba(${backgroundColor})`,
+        },
+      }, value.toFixed(2))
+    );
+  }
+}
+
+const ContrastValue = createFactory(ContrastValueClass);
+
+/**
+ * Component that renders labeled colour contrast values together with the large text
+ * indiscator.
+ */
+class ColorContrastAccessibilityClass extends Component {
+  static get propTypes() {
+    return {
+      error: PropTypes.string,
+      isLargeText: PropTypes.bool.isRequired,
+      color: PropTypes.array.isRequired,
+      value: PropTypes.number,
+      min: PropTypes.number,
+      max: PropTypes.number,
+      backgroundColor: PropTypes.array,
+      backgroundColorMin: PropTypes.array,
+      backgroundColorMax: PropTypes.array,
+    };
+  }
+
+  render() {
+    const {
+      error,
+      isLargeText,
+      color,
+      value, backgroundColor,
+      min, backgroundColorMin,
+      max, backgroundColorMax,
+    } = this.props;
+
+    const children = [];
+
+    if (error) {
+      children.push(span({
+        className: "accessibility-color-contrast-error",
+        role: "presentation",
+      }, L10N.getStr("accessibility.contrast.error")));
+
+      return (div({
+        role: "presentation",
+        className: "accessibility-color-contrast",
+      }, ...children));
+    }
+
+    if (value) {
+      children.push(ContrastValue({ isLargeText, color, backgroundColor, value }));
+    } else {
+      children.push(
+        ContrastValue(
+          { isLargeText, color, backgroundColor: backgroundColorMin, value: min }),
+        div({
+          role: "presentation",
+          className: "accessibility-color-contrast-separator",
+        }),
+        ContrastValue(
+          { isLargeText, color, backgroundColor: backgroundColorMax, value: max }),
+      );
+    }
+
+    if (isLargeText) {
+      children.push(
+        span({
+          className: "accessibility-color-contrast-large-text",
+          role: "presentation",
+          title: L10N.getStr("accessibility.contrast.large.title"),
+        }, L10N.getStr("accessibility.contrast.large.text"))
+      );
+    }
+
+    return (
+      div(
+        {
+          role: "presentation",
+          className: "accessibility-color-contrast",
+        },
+        ...children
+      )
+    );
+  }
+}
+
+const ColorContrastAccessibility = createFactory(ColorContrastAccessibilityClass);
+
+class ContrastAnnotationClass extends Component {
+  static get propTypes() {
+    return {
+      isLargeText: PropTypes.bool.isRequired,
+      value: PropTypes.number,
+      min: PropTypes.number,
+    };
+  }
+
+  render() {
+    const { isLargeText, min, value } = this.props;
+    const score = getContrastRatioScore(value || min, isLargeText);
+
+    return (
+      LearnMoreLink(
+        {
+          className: "accessibility-color-contrast-annotation",
+          href: A11Y_CONTRAST_LEARN_MORE_LINK,
+          learnMoreStringKey: "accessibility.learnMore",
+          l10n: L10N,
+          messageStringKey: `accessibility.contrast.annotation.${score}`,
+        }
+      )
+    );
+  }
+}
+
+const ContrastAnnotation = createFactory(ContrastAnnotationClass);
+
+class ColorContrastCheck extends Component {
+  static get propTypes() {
+    return {
+      error: PropTypes.string.isRequired,
+    };
+  }
+
+  render() {
+    const { error } = this.props;
+
+    return (
+      div({
+        role: "presentation",
+        className: "accessibility-color-contrast-check",
+      },
+        h3({
+          className: "accessibility-color-contrast-header",
+        }, L10N.getStr("accessibility.contrast.header")),
+        ColorContrastAccessibility(this.props),
+        !error && ContrastAnnotation(this.props)
+      )
+    );
+  }
+}
+
+/**
+ * Get contrast ratio score.
+ * ratio.
+ * @param  {Number} value
+ *         Value of the contrast ratio for a given accessible object.
+ * @param  {Boolean} isLargeText
+ *         True if the accessible object contains large text.
+ * @return {String}
+ *         Represents the appropriate contrast ratio score.
+ */
+function getContrastRatioScore(value, isLargeText) {
+  const levels = isLargeText ? { AA: 3, AAA: 4.5 } : { AA: 4.5, AAA: 7 };
+
+  let score = "fail";
+  if (value >= levels.AAA) {
+    score = "AAA";
+  } else if (value >= levels.AA) {
+    score = "AA";
+  }
+
+  return score;
+}
+
+module.exports = {
+  ColorContrastAccessibility: ColorContrastAccessibilityClass,
+  ColorContrastCheck,
+};
--- a/devtools/client/accessibility/constants.js
+++ b/devtools/client/accessibility/constants.js
@@ -63,8 +63,11 @@ exports.ACCESSIBLE_EVENTS = [
 
 // Telemetry name constants.
 exports.A11Y_SERVICE_DURATION = "DEVTOOLS_ACCESSIBILITY_SERVICE_TIME_ACTIVE_SECONDS";
 exports.A11Y_SERVICE_ENABLED_COUNT = "devtools.accessibility.service_enabled_count";
 
 // URL constants
 exports.A11Y_LEARN_MORE_LINK =
   "https://developer.mozilla.org/docs/Tools/Accessibility_inspector";
+exports.A11Y_CONTRAST_LEARN_MORE_LINK =
+  "https://developer.mozilla.org/docs/Web/Accessibility/Understanding_WCAG/Perceivable/" +
+  "Color_contrast?utm_source=devtools&utm_medium=a11y-panel-checks-color-contrast";
--- a/devtools/client/accessibility/test/mochitest/chrome.ini
+++ b/devtools/client/accessibility/test/mochitest/chrome.ini
@@ -1,8 +1,11 @@
 [DEFAULT]
 support-files =
   head.js
+  contrast.snapshots.js
+  !/devtools/client/shared/components/test/mochitest/head.js
 
+[test_accessible_contrast.html]
 [test_accessible_learnMoreLink.html]
 [test_accessible_openLink.html]
 [test_accessible_relations.html]
 [test_accessible_row_context_menu.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/mochitest/contrast.snapshots.js
@@ -0,0 +1,272 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+window._snapshots = {
+  "ColorContrastAccessibility error render.": {
+    "type": "div",
+    "props": {
+      "role": "presentation",
+      "className": "accessibility-color-contrast-check",
+    },
+    "children": [
+      {
+        "type": "h3",
+        "props": {
+          "className": "accessibility-color-contrast-header",
+        },
+        "children": [
+          "Color and Contrast",
+        ],
+      },
+      {
+        "type": "div",
+        "props": {
+          "role": "presentation",
+          "className": "accessibility-color-contrast",
+        },
+        "children": [
+          {
+            "type": "span",
+            "props": {
+              "className": "accessibility-color-contrast-error",
+              "role": "presentation",
+            },
+            "children": [
+              "Unable to calculate",
+            ],
+          },
+        ],
+      },
+    ],
+  },
+  "ColorContrastAccessibility basic render.": {
+    "type": "div",
+    "props": {
+      "role": "presentation",
+      "className": "accessibility-color-contrast-check",
+    },
+    "children": [
+      {
+        "type": "h3",
+        "props": {
+          "className": "accessibility-color-contrast-header",
+        },
+        "children": [
+          "Color and Contrast",
+        ],
+      },
+      {
+        "type": "div",
+        "props": {
+          "role": "presentation",
+          "className": "accessibility-color-contrast",
+        },
+        "children": [
+          {
+            "type": "span",
+            "props": {
+              "className": "accessibility-contrast-value fail",
+              "role": "presentation",
+              "style": {
+                "--accessibility-contrast-color": "rgba(255,0,0,1)",
+                "--accessibility-contrast-bg": "rgba(255,255,255,1)",
+              },
+            },
+            "children": [
+              "4.00",
+            ],
+          },
+        ],
+      },
+      {
+        "type": "p",
+        "props": {
+          "className": "accessibility-color-contrast-annotation",
+        },
+        "children": [
+          "Does not meet WCAG standards for accessible text. ",
+          {
+            "type": "a",
+            "props": {
+              "className": "link",
+              "href": "https://developer.mozilla.org/docs/Web/Accessibility/" +
+                      "Understanding_WCAG/Perceivable/Color_contrast?utm_source=" +
+                      "devtools&utm_medium=a11y-panel-checks-color-contrast",
+              "onClick": "openDocOnClick(event) {\n    event.preventDefault();\n    " +
+                         "openDocLink(event.target.href);\n  }",
+            },
+            "children": [
+              "Learn more",
+            ],
+          },
+          "",
+        ],
+      },
+    ],
+  },
+  "ColorContrastAccessibility range render.": {
+    "type": "div",
+    "props": {
+      "role": "presentation",
+      "className": "accessibility-color-contrast-check",
+    },
+    "children": [
+      {
+        "type": "h3",
+        "props": {
+          "className": "accessibility-color-contrast-header",
+        },
+        "children": [
+          "Color and Contrast",
+        ],
+      },
+      {
+        "type": "div",
+        "props": {
+          "role": "presentation",
+          "className": "accessibility-color-contrast",
+        },
+        "children": [
+          {
+            "type": "span",
+            "props": {
+              "className": "accessibility-contrast-value fail",
+              "role": "presentation",
+              "style": {
+                "--accessibility-contrast-color": "rgba(128,128,128,1)",
+                "--accessibility-contrast-bg": "rgba(219,106,116,1)",
+              },
+            },
+            "children": [
+              "1.19",
+            ],
+          },
+          {
+            "type": "div",
+            "props": {
+              "role": "presentation",
+              "className": "accessibility-color-contrast-separator",
+            },
+            "children": null,
+          },
+          {
+            "type": "span",
+            "props": {
+              "className": "accessibility-contrast-value fail",
+              "role": "presentation",
+              "style": {
+                "--accessibility-contrast-color": "rgba(128,128,128,1)",
+                "--accessibility-contrast-bg": "rgba(156,145,211,1)",
+              },
+            },
+            "children": [
+              "1.39",
+            ],
+          },
+        ],
+      },
+      {
+        "type": "p",
+        "props": {
+          "className": "accessibility-color-contrast-annotation",
+        },
+        "children": [
+          "Does not meet WCAG standards for accessible text. ",
+          {
+            "type": "a",
+            "props": {
+              "className": "link",
+              "href": "https://developer.mozilla.org/docs/Web/Accessibility/" +
+                      "Understanding_WCAG/Perceivable/Color_contrast?utm_source=" +
+                      "devtools&utm_medium=a11y-panel-checks-color-contrast",
+              "onClick": "openDocOnClick(event) {\n    event.preventDefault();\n    " +
+                         "openDocLink(event.target.href);\n  }",
+            },
+            "children": [
+              "Learn more",
+            ],
+          },
+          "",
+        ],
+      },
+    ],
+  },
+  "ColorContrastAccessibility large text render.": {
+    "type": "div",
+    "props": {
+      "role": "presentation",
+      "className": "accessibility-color-contrast-check",
+    },
+    "children": [
+      {
+        "type": "h3",
+        "props": {
+          "className": "accessibility-color-contrast-header",
+        },
+        "children": [
+          "Color and Contrast",
+        ],
+      },
+      {
+        "type": "div",
+        "props": {
+          "role": "presentation",
+          "className": "accessibility-color-contrast",
+        },
+        "children": [
+          {
+            "type": "span",
+            "props": {
+              "className": "accessibility-contrast-value AA",
+              "role": "presentation",
+              "style": {
+                "--accessibility-contrast-color": "rgba(255,0,0,1)",
+                "--accessibility-contrast-bg": "rgba(255,255,255,1)",
+              },
+            },
+            "children": [
+              "4.00",
+            ],
+          },
+          {
+            "type": "span",
+            "props": {
+              "className": "accessibility-color-contrast-large-text",
+              "role": "presentation",
+              "title": "Text is 14 point and bold or larger, or 18 point or larger.",
+            },
+            "children": [
+              "large text",
+            ],
+          },
+        ],
+      },
+      {
+        "type": "p",
+        "props": {
+          "className": "accessibility-color-contrast-annotation",
+        },
+        "children": [
+          "Meets WCAG AA standards for accessible text. ",
+          {
+            "type": "a",
+            "props": {
+              "className": "link",
+              "href": "https://developer.mozilla.org/docs/Web/Accessibility/" +
+                      "Understanding_WCAG/Perceivable/Color_contrast?utm_source=" +
+                      "devtools&utm_medium=a11y-panel-checks-color-contrast",
+              "onClick": "openDocOnClick(event) {\n    event.preventDefault();\n    " +
+                         "openDocLink(event.target.href);\n  }",
+            },
+            "children": [
+              "Learn more",
+            ],
+          },
+          "",
+        ],
+      },
+    ],
+  },
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/mochitest/test_accessible_contrast.html
@@ -0,0 +1,75 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that Color Contrast component renders correctly.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Color Contrast accessibility component test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript"></script>
+<script src="chrome://mochitests/content/chrome/devtools/client/shared/components/test/mochitest/head.js" type="application/javascript"/>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" type="application/javascript"></script>
+<script src="contrast.snapshots.js" type="application/javascript"></script>
+<script type="application/javascript">
+
+"use strict";
+
+/* global matchSnapshot */
+
+window.onload = async function() {
+  try {
+    const React = browserRequire("devtools/client/shared/vendor/react");
+    const { ColorContrastCheck } = browserRequire(
+      "devtools/client/accessibility/components/ColorContrastAccessibility");
+
+    matchSnapshot("ColorContrastAccessibility error render.",
+      React.createElement(ColorContrastCheck, { error: true })
+    );
+
+    matchSnapshot("ColorContrastAccessibility basic render.",
+      React.createElement(ColorContrastCheck, {
+        "value": 4.00,
+        "color": [255, 0, 0, 1],
+        "backgroundColor": [255, 255, 255, 1],
+        "isLargeText": false,
+      })
+    );
+
+    matchSnapshot("ColorContrastAccessibility range render.",
+      React.createElement(ColorContrastCheck, {
+        "min": 1.19,
+        "max": 1.39,
+        "color": [128, 128, 128, 1],
+        "backgroundColorMin": [219, 106, 116, 1],
+        "backgroundColorMax": [156, 145, 211, 1],
+        "isLargeText": false,
+      })
+    );
+
+    matchSnapshot("ColorContrastAccessibility large text render.",
+      React.createElement(ColorContrastCheck, {
+        "value": 4.00,
+        "color": [255, 0, 0, 1],
+        "backgroundColor": [255, 255, 255, 1],
+        "isLargeText": true,
+      })
+    );
+  } catch (e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+};
+</script>
+</pre>
+</body>
+</html>
--- a/devtools/client/locales/en-US/accessibility.properties
+++ b/devtools/client/locales/en-US/accessibility.properties
@@ -101,8 +101,44 @@ accessibility.description.general.p2=Acc
 # when accessibility service description is provided when a client is connected
 # to an older version of accessibility actor.
 accessibility.description.oldVersion=You are connected to a debugger server that is too old. To use Accessibility panel, please connect to the latest debugger server version.
 
 # LOCALIZATION NOTE (accessibility.tree.menu.printToJSON): A title text used when a
 # context menu item for printing an accessible tree to JSON is rendered after triggering a
 # context menu for an accessible tree row.
 accessibility.tree.menu.printToJSON=Print to JSON
+
+# LOCALIZATION NOTE (accessibility.contrast.header): A title text used for header for
+# checks related to color and contrast.
+accessibility.contrast.header=Color and Contrast
+
+# LOCALIZATION NOTE (accessibility.contrast.error): A title text for the color
+# contrast ratio, used when the tool is unable to calculate the contrast ratio value.
+accessibility.contrast.error=Unable to calculate
+
+# LOCALIZATION NOTE (accessibility.contrast.large.text): A title text for the color
+# contrast ratio label indicating that the color contrast criteria used is if for large
+# text. This is lower case because it's used as a label for a tree item in accessibility
+# tree.
+accessibility.contrast.large.text=large text
+
+# LOCALIZATION NOTE (accessibility.contrast.large.title): A title text for the tooltip
+# used for the large text label (see accessibility.contrast.large.text).
+accessibility.contrast.large.title=Text is 14 point and bold or larger, or 18 point or larger.
+
+# LOCALIZATION NOTE (accessibility.contrast.annotation.AA): A title text for the paragraph
+# describing that the given colour contrast satisfies AA standard from Web Content
+# Accessibility Guidelines. %S in the content will be replaced by a link at run time
+# with the accessibility.learnMore string.
+accessibility.contrast.annotation.AA=Meets WCAG AA standards for accessible text. %S
+
+# LOCALIZATION NOTE (accessibility.contrast.annotation.AAA): A title text for the
+# paragraph describing that the given colour contrast satisfies AAA standard from Web
+# Content Accessibility Guidelines. %S in the content will be replaced by a link at run
+# time with the accessibility.learnMore string.
+accessibility.contrast.annotation.AAA=Meets WCAG AAA standards for accessible text. %S
+
+# LOCALIZATION NOTE (accessibility.contrast.annotation.fail): A title text for the
+# paragraph describing that the given colour contrast fails to meet the minimum level from
+# Web Content Accessibility Guidelines. %S in the content will be replaced by a link at
+# run time with the accessibility.learnMore string.
+accessibility.contrast.annotation.fail=Does not meet WCAG standards for accessible text. %S