Repository: mui/material-ui (TypeScript)
Date: 2021-06-03 07:21:16+00:00
Task type: Bug Fix

Issue:
[docs] missing role="link" for Button with Link
<!-- Provide a general summary of the issue in the Title above -->
Using the documented way of creating links styled as buttons as seen in https://next.material-ui.com/guides/routing/#button adds wrong role on the rendered element. Specifically it adds `role="button"` making the link undiscoverable from google crawlers. I would at least hope that it was documented that I have to manually type `role="link"` to make links discoverable.

<!-- Checked checkbox should look like this: [x] -->

- [x] The issue is present in the latest release.
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯
Using `<Button component={RouterLink} to="/">` I get an `<a href="/" role="button">` rendered.
The assigned accessibility role is "button"

## Expected Behavior 🤔

Using `<Button component={RouterLink} to="/">` I expect to have an accessibility role of "link"

## Steps to Reproduce 🕹

Steps:

1. Go to https://next.material-ui.com/guides/routing/#button
2. Inspect the rendered `<a>` and observe an incorrect role attribute


## Your Environment 🌎

v5.0.0-alpha.34
react-router-dom v6 beta.0


Issue discussion:
Nice, do you want to submit a PR. I think it can be added to https://next.material-ui.com/guides/routing/#button in `Button` and `List` code example

The file in the codebase is `docs/src/pages/guides/routing/routing.md`

What about this heuristic (in the ButtonBase)?

```diff
diff --git a/docs/src/pages/guides/routing/ButtonRouter.tsx b/docs/src/pages/guides/routing/ButtonRouter.tsx
index 0cbcb17272..fa4c0567b5 100644
--- a/docs/src/pages/guides/routing/ButtonRouter.tsx
+++ b/docs/src/pages/guides/routing/ButtonRouter.tsx
@@ -5,7 +5,12 @@ import Button from '@material-ui/core/Button';

 const LinkBehavior = React.forwardRef<any, Omit<RouterLinkProps, 'to'>>(
   (props, ref) => (
-    <RouterLink ref={ref} to="/getting-started/installation/" {...props} />
+    <RouterLink
+      ref={ref}
+      to="/getting-started/installation/"
+      {...props}
+      role={undefined}
+    />
   ),
 );

diff --git a/docs/src/pages/guides/routing/ListRouter.tsx b/docs/src/pages/guides/routing/ListRouter.tsx
index 2c8b75dcb5..78525951da 100644
--- a/docs/src/pages/guides/routing/ListRouter.tsx
+++ b/docs/src/pages/guides/routing/ListRouter.tsx
@@ -27,7 +27,7 @@ function ListItemLink(props: ListItemLinkProps) {
         itemProps,
         ref,
       ) {
-        return <RouterLink to={to} ref={ref} {...itemProps} />;
+        return <RouterLink to={to} ref={ref} {...itemProps} role={undefined} />;
       }),
     [to],
   );
diff --git a/packages/material-ui/src/ButtonBase/ButtonBase.js b/packages/material-ui/src/ButtonBase/ButtonBase.js
index 3a9aba8b0b..092847c4d5 100644
--- a/packages/material-ui/src/ButtonBase/ButtonBase.js
+++ b/packages/material-ui/src/ButtonBase/ButtonBase.js
@@ -292,7 +292,7 @@ const ButtonBase = React.forwardRef(function ButtonBase(inProps, ref) {
     buttonProps.type = type === undefined ? 'button' : type;
     buttonProps.disabled = disabled;
   } else {
-    if (!other.href) {
+    if (!other.href && !other.to) {
       buttonProps.role = 'button';
     }
     if (disabled) {
```
@siriwatknp  I don't have time for a PR. Someone else can take it!
I would like to take this one, please.
PS: And I am very new to OS so please forgive me for any mistakes and asking more questions.
This is what I am thinking to include to explain the ` role='link' ` issue

![screenshot-localhost_3000-2021 05 27-00_08_25](https://user-images.githubusercontent.com/31199288/119713787-bb740900-be7f-11eb-9817-872a1352b757.png)

@shadab14meb346 Are you working on this issue, if not I can take this issue.
I think it's a bad idea to let the developer choose the role for links. I like the heuristic to simply remove the role="button" when the `to` prop is detected on the ButtonBase. A lot of frontend developers don't care about accessibility and so I believe that this issue must be handled automatically and no change in documentation should be necessary.
@shadab14meb346 For the change in the documentation, I think that it's a good initiative, but we likely want to keep it straight:

```
> **Note**:  When the `component` prop is used on the `ButtonBase` and not indicators are
given that a link is rendered, a `role="button"` prop is provided by the `ButtonBase` to the component. 
You need to swallow this attribute if you are rendering a link, e.g. with `role={undefined}`.
```

> and no change in documentation should be necessary

@itsUndefined I think that we have to change the documentation, as our least worse option. We can't know if the component is used to render a button or a link. Not adding a role button would be even worse for when developers render a `div`.
@oliviertassinari  I have included the Note as you suggested.
This is how it looks like now. If it looks fine please let me know? I will go ahead and do the PR.

![screenshot-localhost_3000-2021 05 27-09_05_28](https://user-images.githubusercontent.com/31199288/119762117-bab49480-beca-11eb-99f6-8e6d7bbb8552.png)

@shadab14meb346 All good from my end 👍 

Solution patch:
diff --git a/docs/src/pages/guides/routing/ButtonDemo.js b/docs/src/pages/guides/routing/ButtonDemo.js
--- a/docs/src/pages/guides/routing/ButtonDemo.js
+++ b/docs/src/pages/guides/routing/ButtonDemo.js
@@ -1,15 +1,10 @@
 import * as React from 'react';
-import Box from '@material-ui/core/Box';
 import Button from '@material-ui/core/Button';
 
-const preventDefault = (event) => event.preventDefault();
-
 export default function ButtonDemo() {
   return (
-    <Box role="presentation" onClick={preventDefault}>
-      <Button href="/" variant="contained">
-        Link
-      </Button>
-    </Box>
+    <Button href="/" variant="contained">
+      Link
+    </Button>
   );
 }
diff --git a/docs/src/pages/guides/routing/ButtonDemo.tsx b/docs/src/pages/guides/routing/ButtonDemo.tsx
--- a/docs/src/pages/guides/routing/ButtonDemo.tsx
+++ b/docs/src/pages/guides/routing/ButtonDemo.tsx
@@ -1,15 +1,10 @@
 import * as React from 'react';
-import Box from '@material-ui/core/Box';
 import Button from '@material-ui/core/Button';
 
-const preventDefault = (event: React.SyntheticEvent) => event.preventDefault();
-
 export default function ButtonDemo() {
   return (
-    <Box role="presentation" onClick={preventDefault}>
-      <Button href="/" variant="contained">
-        Link
-      </Button>
-    </Box>
+    <Button href="/" variant="contained">
+      Link
+    </Button>
   );
 }
diff --git a/docs/src/pages/guides/routing/ButtonRouter.js b/docs/src/pages/guides/routing/ButtonRouter.js
--- a/docs/src/pages/guides/routing/ButtonRouter.js
+++ b/docs/src/pages/guides/routing/ButtonRouter.js
@@ -4,7 +4,7 @@ import { Link as RouterLink } from 'react-router-dom';
 import Button from '@material-ui/core/Button';
 
 const LinkBehavior = React.forwardRef((props, ref) => (
-  <RouterLink ref={ref} to="/getting-started/installation/" {...props} />
+  <RouterLink ref={ref} to="/" {...props} role={undefined} />
 ));
 
 export default function ButtonRouter() {
@@ -15,7 +15,7 @@ export default function ButtonRouter() {
           With prop forwarding
         </Button>
         <br />
-        <Button component={LinkBehavior}>Without prop forwarding</Button>
+        <Button component={LinkBehavior}>With inlining</Button>
       </Router>
     </div>
   );
diff --git a/docs/src/pages/guides/routing/ButtonRouter.tsx b/docs/src/pages/guides/routing/ButtonRouter.tsx
--- a/docs/src/pages/guides/routing/ButtonRouter.tsx
+++ b/docs/src/pages/guides/routing/ButtonRouter.tsx
@@ -4,9 +4,7 @@ import { Link as RouterLink, LinkProps as RouterLinkProps } from 'react-router-d
 import Button from '@material-ui/core/Button';
 
 const LinkBehavior = React.forwardRef<any, Omit<RouterLinkProps, 'to'>>(
-  (props, ref) => (
-    <RouterLink ref={ref} to="/getting-started/installation/" {...props} />
-  ),
+  (props, ref) => <RouterLink ref={ref} to="/" {...props} role={undefined} />,
 );
 
 export default function ButtonRouter() {
@@ -17,7 +15,7 @@ export default function ButtonRouter() {
           With prop forwarding
         </Button>
         <br />
-        <Button component={LinkBehavior}>Without prop forwarding</Button>
+        <Button component={LinkBehavior}>With inlining</Button>
       </Router>
     </div>
   );
diff --git a/docs/src/pages/guides/routing/LinkDemo.js b/docs/src/pages/guides/routing/LinkDemo.js
--- a/docs/src/pages/guides/routing/LinkDemo.js
+++ b/docs/src/pages/guides/routing/LinkDemo.js
@@ -2,11 +2,9 @@ import * as React from 'react';
 import Link from '@material-ui/core/Link';
 import Box from '@material-ui/core/Box';
 
-const preventDefault = (event) => event.preventDefault();
-
 export default function LinkDemo() {
   return (
-    <Box sx={{ typography: 'body1' }} role="presentation" onClick={preventDefault}>
+    <Box sx={{ typography: 'body1' }}>
       <Link href="/">Link</Link>
     </Box>
   );
diff --git a/docs/src/pages/guides/routing/LinkDemo.tsx b/docs/src/pages/guides/routing/LinkDemo.tsx
--- a/docs/src/pages/guides/routing/LinkDemo.tsx
+++ b/docs/src/pages/guides/routing/LinkDemo.tsx
@@ -2,11 +2,9 @@ import * as React from 'react';
 import Link from '@material-ui/core/Link';
 import Box from '@material-ui/core/Box';
 
-const preventDefault = (event: React.SyntheticEvent) => event.preventDefault();
-
 export default function LinkDemo() {
   return (
-    <Box sx={{ typography: 'body1' }} role="presentation" onClick={preventDefault}>
+    <Box sx={{ typography: 'body1' }}>
       <Link href="/">Link</Link>
     </Box>
   );
diff --git a/docs/src/pages/guides/routing/ListRouter.js b/docs/src/pages/guides/routing/ListRouter.js
--- a/docs/src/pages/guides/routing/ListRouter.js
+++ b/docs/src/pages/guides/routing/ListRouter.js
@@ -19,7 +19,7 @@ function ListItemLink(props) {
   const renderLink = React.useMemo(
     () =>
       React.forwardRef(function Link(itemProps, ref) {
-        return <RouterLink to={to} ref={ref} {...itemProps} />;
+        return <RouterLink to={to} ref={ref} {...itemProps} role={undefined} />;
       }),
     [to],
   );
diff --git a/docs/src/pages/guides/routing/ListRouter.tsx b/docs/src/pages/guides/routing/ListRouter.tsx
--- a/docs/src/pages/guides/routing/ListRouter.tsx
+++ b/docs/src/pages/guides/routing/ListRouter.tsx
@@ -27,7 +27,7 @@ function ListItemLink(props: ListItemLinkProps) {
         itemProps,
         ref,
       ) {
-        return <RouterLink to={to} ref={ref} {...itemProps} />;
+        return <RouterLink to={to} ref={ref} {...itemProps} role={undefined} />;
       }),
     [to],
   );
diff --git a/docs/src/pages/guides/routing/routing.md b/docs/src/pages/guides/routing/routing.md
--- a/docs/src/pages/guides/routing/routing.md
+++ b/docs/src/pages/guides/routing/routing.md
@@ -58,23 +58,34 @@ const theme = createTheme({
 ## `component` prop
 
 You can achieve the integration with third-party routing libraries with the `component` prop.
-You can learn more about this prop in the [composition guide](/guides/composition/#component-prop).
+You can learn more about this prop in the [**composition guide**](/guides/composition/#component-prop).
+
+### Link
 
 Here are a few demos with [react-router](https://github.com/ReactTraining/react-router).
 You can apply the same strategy with all the components: BottomNavigation, Card, etc.
 
-### Link
-
 {{"demo": "pages/guides/routing/LinkRouter.js"}}
 
-### Tabs
-
-{{"demo": "pages/guides/routing/TabsRouter.js", "defaultCodeOpen": false}}
-
 ### Button
 
 {{"demo": "pages/guides/routing/ButtonRouter.js"}}
 
+**Note**: The button base component adds the `role="button"` attribute when it identifies the intent to render a button without a native `<button>` element.
+This can create issues when rendering a link.
+If you are not using one of the `href`, `to`, or `component="a"` props, you need to override the `role` attribute.
+The above demo achieves this by setting `role={undefined}` **after** the spread props.
+
+```jsx
+const LinkBehavior = React.forwardRef((props, ref) => (
+  <RouterLink ref={ref} to="/" {...props} role={undefined} />
+));
+```
+
+### Tabs
+
+{{"demo": "pages/guides/routing/TabsRouter.js", "defaultCodeOpen": false}}
+
 ### List
 
 {{"demo": "pages/guides/routing/ListRouter.js"}}
diff --git a/packages/material-ui/src/ButtonBase/ButtonBase.js b/packages/material-ui/src/ButtonBase/ButtonBase.js
--- a/packages/material-ui/src/ButtonBase/ButtonBase.js
+++ b/packages/material-ui/src/ButtonBase/ButtonBase.js
@@ -283,7 +283,7 @@ const ButtonBase = React.forwardRef(function ButtonBase(inProps, ref) {
 
   let ComponentProp = component;
 
-  if (ComponentProp === 'button' && other.href) {
+  if (ComponentProp === 'button' && (other.href || other.to)) {
     ComponentProp = LinkComponent;
   }
 
@@ -292,7 +292,7 @@ const ButtonBase = React.forwardRef(function ButtonBase(inProps, ref) {
     buttonProps.type = type === undefined ? 'button' : type;
     buttonProps.disabled = disabled;
   } else {
-    if (!other.href) {
+    if (!other.href && !other.to) {
       buttonProps.role = 'button';
     }
     if (disabled) {


Test patch:
diff --git a/packages/material-ui/src/ButtonBase/ButtonBase.test.js b/packages/material-ui/src/ButtonBase/ButtonBase.test.js
--- a/packages/material-ui/src/ButtonBase/ButtonBase.test.js
+++ b/packages/material-ui/src/ButtonBase/ButtonBase.test.js
@@ -133,7 +133,7 @@ describe('<ButtonBase />', () => {
       expect(button).not.to.have.attribute('type');
     });
 
-    it('should not add role="button" if custom LinkComponent and href are used', () => {
+    it('should not add role="button" if custom component and href are used', () => {
       const CustomLink = React.forwardRef((props, ref) => {
         return (
           <a data-testid="customLink" ref={ref} {...props}>
@@ -143,7 +143,8 @@ describe('<ButtonBase />', () => {
       });
 
       const { container } = render(
-        <ButtonBase LinkComponent={CustomLink} href="https://google.com">
+        // @ts-expect-error missing types in CustomLink
+        <ButtonBase component={CustomLink} href="https://google.com">
           Hello
         </ButtonBase>,
       );
@@ -153,6 +154,27 @@ describe('<ButtonBase />', () => {
       expect(button).to.have.attribute('href', 'https://google.com');
       expect(button).not.to.have.attribute('role', 'button');
     });
+
+    it('should not add role="button" if custom component and to are used', () => {
+      const CustomLink = React.forwardRef((props, ref) => {
+        // @ts-expect-error missing types in CustomLink
+        const { to, ...other } = props;
+        return (
+          <a data-testid="customLink" ref={ref} {...other} href={to}>
+            {props.children}
+          </a>
+        );
+      });
+
+      const { container } = render(
+        // @ts-expect-error missing types in CustomLink
+        <ButtonBase component={CustomLink} to="https://google.com">
+          Hello
+        </ButtonBase>,
+      );
+      const button = container.firstChild;
+      expect(button).not.to.have.attribute('role', 'button');
+    });
   });
 
   describe('event callbacks', () => {