Repository: grommet/grommet (JavaScript)

Issue:
RangeInput does not support a11yTitle
### Expected Behavior
RangeInput should support the `a11yTitle` prop for accessibility

### Actual Behavior
RangeInput does not support the `a11yTitle` prop. The only way to have a label to to set the `aria-label` directly

### URL, screen shot, or Codepen exhibiting the issue
<img width="473" alt="Screen Shot 2020-07-09 at 3 43 12 PM" src="https://user-images.githubusercontent.com/54560994/87093792-01d66000-c1fb-11ea-9c5d-fd58fb053c3e.png">
<img width="561" alt="Screen Shot 2020-07-09 at 3 43 30 PM" src="https://user-images.githubusercontent.com/54560994/87093804-069b1400-c1fb-11ea-9316-91ee78a2887c.png">


Discussion:

Patch:
diff --git a/src/js/components/RangeInput/README.md b/src/js/components/RangeInput/README.md
index 4a29bbdb630..ababc85d420 100644
--- a/src/js/components/RangeInput/README.md
+++ b/src/js/components/RangeInput/README.md
@@ -11,6 +11,15 @@ import { RangeInput } from 'grommet';
 
 ## Properties
 
+**a11yTitle**
+
+Custom label to be used by screen readers.
+      When provided, an aria-label will be added to the element.
+
+```
+string
+```
+
 **id**
 
 The id attribute of the range input.
diff --git a/src/js/components/RangeInput/RangeInput.js b/src/js/components/RangeInput/RangeInput.js
index 794dad101a2..a4e5b2dfa23 100644
--- a/src/js/components/RangeInput/RangeInput.js
+++ b/src/js/components/RangeInput/RangeInput.js
@@ -4,7 +4,10 @@ import { FormContext } from '../Form/FormContext';
 import { StyledRangeInput } from './StyledRangeInput';
 
 const RangeInput = forwardRef(
-  ({ name, onChange, onFocus, onBlur, value: valueProp, ...rest }, ref) => {
+  (
+    { a11yTitle, name, onChange, onFocus, onBlur, value: valueProp, ...rest },
+    ref,
+  ) => {
     const formContext = useContext(FormContext);
 
     const [value, setValue] = formContext.useFormInput(name, valueProp);
@@ -12,6 +15,7 @@ const RangeInput = forwardRef(
     const [focus, setFocus] = useState();
     return (
       <StyledRangeInput
+        aria-label={a11yTitle}
         ref={ref}
         name={name}
         focus={focus}
diff --git a/src/js/components/RangeInput/doc.js b/src/js/components/RangeInput/doc.js
index 613dd0cb08a..ab15ba04421 100644
--- a/src/js/components/RangeInput/doc.js
+++ b/src/js/components/RangeInput/doc.js
@@ -13,6 +13,10 @@ export const doc = RangeInput => {
     .intrinsicElement('input');
 
   DocumentedRangeInput.propTypes = {
+    a11yTitle: PropTypes.string.description(
+      `Custom label to be used by screen readers.
+      When provided, an aria-label will be added to the element.`,
+    ),
     id: PropTypes.string.description('The id attribute of the range input.'),
     min: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).description(
       'The minimum value permitted.',
diff --git a/src/js/components/RangeInput/index.d.ts b/src/js/components/RangeInput/index.d.ts
index 57006dd92f5..b852605c44d 100644
--- a/src/js/components/RangeInput/index.d.ts
+++ b/src/js/components/RangeInput/index.d.ts
@@ -1,6 +1,8 @@
 import * as React from "react";
+import { A11yTitleType } from "../../utils";
 
 export interface RangeInputProps {
+  a11yTitle?: A11yTitleType;
   id?: string;
   min?: number | string;
   max?: number | string;


Test patch:
diff --git a/src/js/components/RangeInput/__tests__/RangeInput-test.js b/src/js/components/RangeInput/__tests__/RangeInput-test.js
index c7f0ec6d20c..7c5cd5fcdc7 100644
--- a/src/js/components/RangeInput/__tests__/RangeInput-test.js
+++ b/src/js/components/RangeInput/__tests__/RangeInput-test.js
@@ -1,48 +1,68 @@
 import React from 'react';
 import renderer from 'react-test-renderer';
 import 'jest-styled-components';
+import 'jest-axe/extend-expect';
+import 'regenerator-runtime/runtime';
 
+import { axe } from 'jest-axe';
+import { cleanup, render } from '@testing-library/react';
 import { Grommet } from '../../Grommet';
 import { RangeInput } from '..';
 
-test('RangeInput renders', () => {
-  const component = renderer.create(
-    <Grommet>
-      <RangeInput value="50" />
-    </Grommet>,
-  );
-  const tree = component.toJSON();
-  expect(tree).toMatchSnapshot();
-});
+describe('RangeInput', () => {
+  afterEach(cleanup);
 
-test('RangeInput track themed', () => {
-  const component = renderer.create(
-    <Grommet theme={{ rangeInput: { track: { color: 'brand' } } }}>
-      <RangeInput value="10" />
-    </Grommet>,
-  );
-  const tree = component.toJSON();
-  expect(tree).toMatchSnapshot();
-});
+  test('should have no accessibility violations', async () => {
+    const { container } = render(
+      <Grommet>
+        <RangeInput value="50" a11yTitle="test" />
+      </Grommet>,
+    );
 
-test('RangeInput track themed with color and opacity', () => {
-  const component = renderer.create(
-    <Grommet
-      theme={{ rangeInput: { track: { color: 'brand', opacity: 0.3 } } }}
-    >
-      <RangeInput value="10" />
-    </Grommet>,
-  );
-  const tree = component.toJSON();
-  expect(tree).toMatchSnapshot();
-});
+    const results = await axe(container);
+    expect(results).toHaveNoViolations();
+    expect(container).toMatchSnapshot();
+  });
+
+  test('renders', () => {
+    const component = renderer.create(
+      <Grommet>
+        <RangeInput value="50" />
+      </Grommet>,
+    );
+    const tree = component.toJSON();
+    expect(tree).toMatchSnapshot();
+  });
+
+  test('track themed', () => {
+    const component = renderer.create(
+      <Grommet theme={{ rangeInput: { track: { color: 'brand' } } }}>
+        <RangeInput value="10" />
+      </Grommet>,
+    );
+    const tree = component.toJSON();
+    expect(tree).toMatchSnapshot();
+  });
+
+  test('track themed with color and opacity', () => {
+    const component = renderer.create(
+      <Grommet
+        theme={{ rangeInput: { track: { color: 'brand', opacity: 0.3 } } }}
+      >
+        <RangeInput value="10" />
+      </Grommet>,
+    );
+    const tree = component.toJSON();
+    expect(tree).toMatchSnapshot();
+  });
 
-test('with min and max offset', () => {
-  const component = renderer.create(
-    <Grommet>
-      <RangeInput min={10} max={20} step={1} value={15} />
-    </Grommet>,
-  );
-  const tree = component.toJSON();
-  expect(tree).toMatchSnapshot();
+  test('with min and max offset', () => {
+    const component = renderer.create(
+      <Grommet>
+        <RangeInput min={10} max={20} step={1} value={15} />
+      </Grommet>,
+    );
+    const tree = component.toJSON();
+    expect(tree).toMatchSnapshot();
+  });
 });
diff --git a/src/js/components/RangeInput/__tests__/__snapshots__/RangeInput-test.js.snap b/src/js/components/RangeInput/__tests__/__snapshots__/RangeInput-test.js.snap
index aaec786e7b6..06658df3509 100644
--- a/src/js/components/RangeInput/__tests__/__snapshots__/RangeInput-test.js.snap
+++ b/src/js/components/RangeInput/__tests__/__snapshots__/RangeInput-test.js.snap
@@ -133,6 +133,139 @@ exports[`RangeInput renders 1`] = `
 </div>
 `;
 
+exports[`RangeInput should have no accessibility violations 1`] = `
+.c0 {
+  font-size: 18px;
+  line-height: 24px;
+  box-sizing: border-box;
+  -webkit-text-size-adjust: 100%;
+  -ms-text-size-adjust: 100%;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+}
+
+.c1 {
+  box-sizing: border-box;
+  position: relative;
+  -webkit-appearance: none;
+  border-color: transparent;
+  height: 24px;
+  width: 100%;
+  padding: 0px;
+  cursor: pointer;
+  background: transparent;
+}
+
+.c1::-moz-focus-inner {
+  border: none;
+}
+
+.c1::-moz-focus-outer {
+  border: none;
+}
+
+.c1::-webkit-slider-runnable-track {
+  box-sizing: border-box;
+  width: 100%;
+  height: 4px;
+  background: rgba(0,0,0,0.2);
+}
+
+.c1::-webkit-slider-thumb {
+  margin-top: -10px;
+  box-sizing: border-box;
+  position: relative;
+  border-radius: 24px;
+  height: 24px;
+  width: 24px;
+  overflow: visible;
+  background: #7D4CDB;
+  -webkit-appearance: none;
+  cursor: pointer;
+}
+
+.c1::-webkit-slider-thumb:hover {
+  box-shadow: 0px 0px 0px 2px #7D4CDB;
+}
+
+.c1::-moz-range-track {
+  box-sizing: border-box;
+  width: 100%;
+  height: 4px;
+  background: rgba(0,0,0,0.2);
+}
+
+.c1::-moz-range-thumb {
+  box-sizing: border-box;
+  position: relative;
+  border-radius: 24px;
+  height: 24px;
+  width: 24px;
+  overflow: visible;
+  background: #7D4CDB;
+  -webkit-appearance: none;
+  cursor: pointer;
+  margin-top: 0px;
+  height: 24px;
+  width: 24px;
+}
+
+.c1::-ms-thumb {
+  box-sizing: border-box;
+  position: relative;
+  border-radius: 24px;
+  height: 24px;
+  width: 24px;
+  overflow: visible;
+  background: #7D4CDB;
+  -webkit-appearance: none;
+  cursor: pointer;
+  margin-top: 0px;
+  height: 24px;
+  width: 24px;
+}
+
+.c1:hover::-moz-range-thumb {
+  box-shadow: 0px 0px 0px 2px #7D4CDB;
+}
+
+.c1:hover::-ms-thumb {
+  box-shadow: 0px 0px 0px 2px #7D4CDB;
+}
+
+.c1::-ms-track {
+  box-sizing: border-box;
+  width: 100%;
+  height: 4px;
+  background: rgba(0,0,0,0.2);
+  border-color: transparent;
+  color: transparent;
+}
+
+.c1::-ms-fill-lower {
+  background: rgba(0,0,0,0.2);
+  border-color: transparent;
+}
+
+.c1::-ms-fill-upper {
+  background: rgba(0,0,0,0.2);
+  border-color: transparent;
+}
+
+<div>
+  <div
+    class="c0"
+  >
+    <input
+      aria-label="test"
+      class="c1"
+      type="range"
+      value="50"
+    />
+  </div>
+</div>
+`;
+
 exports[`RangeInput track themed 1`] = `
 .c0 {
   font-size: 18px;
@@ -399,7 +532,7 @@ exports[`RangeInput track themed with color and opacity 1`] = `
 </div>
 `;
 
-exports[`with min and max offset 1`] = `
+exports[`RangeInput with min and max offset 1`] = `
 .c0 {
   font-size: 18px;
   line-height: 24px;
diff --git a/src/js/components/__tests__/__snapshots__/README-test.js.snap b/src/js/components/__tests__/__snapshots__/README-test.js.snap
index 0a19dd09ae8..840cdf494d9 100644
--- a/src/js/components/__tests__/__snapshots__/README-test.js.snap
+++ b/src/js/components/__tests__/__snapshots__/README-test.js.snap
@@ -11764,6 +11764,15 @@ import { RangeInput } from 'grommet';
 
 ## Properties
 
+**a11yTitle**
+
+Custom label to be used by screen readers.
+      When provided, an aria-label will be added to the element.
+
+\`\`\`
+string
+\`\`\`
+
 **id**
 
 The id attribute of the range input.
diff --git a/src/js/components/__tests__/__snapshots__/components-test.js.snap b/src/js/components/__tests__/__snapshots__/components-test.js.snap
index 6428c2510fe..2356ab2baf4 100644
--- a/src/js/components/__tests__/__snapshots__/components-test.js.snap
+++ b/src/js/components/__tests__/__snapshots__/components-test.js.snap
@@ -5539,6 +5539,12 @@ object",
     "intrinsicElement": "input",
     "name": "RangeInput",
     "properties": Array [
+      Object {
+        "description": "Custom label to be used by screen readers.
+      When provided, an aria-label will be added to the element.",
+        "format": "string",
+        "name": "a11yTitle",
+      },
       Object {
         "description": "The id attribute of the range input.",
         "format": "string",