reactjsmaterial-ui

Is it possible to render a tooltip on a disabled Material UI Button within a ButtonGroup without breaking the layout?


I'm trying to create a Material UI ButtonGroup with disabled buttons and tooltip.

The following code block shows the buttons correctly, but as described here (https://material-ui.com/components/tooltips/#disabled-elements) disabled elements cannot be provided with a tooltip.

<ButtonGroup>
    <Tooltip title={"This is button A"}>
        <Button>{"Button A"}</Button>
    </Tooltip>
    <Tooltip title={"This is button B"}>
        <Button disabled>{"Button B"}</Button>
    </Tooltip>
</ButtonGroup>

But if I add a span around the disabled button the group layout will be destroyed.

<ButtonGroup>
    <Tooltip title={"This is button A"}>
        <Button>{"Button A"}</Button>
    </Tooltip>
    <Tooltip title={"This is button B"}>
        <span>
            <Button disabled>{"Button B"}</Button>
        </span>
    </Tooltip>
</ButtonGroup>

Solution

  • There are two aspects about a disabled button that get in the way of the tooltip behavior:

    1. As mentioned in the docs you referenced, a disabled <button> element (independent of Material-UI) does not fire events in a manner to support proper behavior of the Tooltip.
    2. Material-UI specifically disables pointer-events in Material-UI's ButtonBase component (leveraged by Button) when it is disabled.

    The second problem can be addressed by overriding Material-UI's disabled styling to allow pointer events:

    import MuiButton from "@material-ui/core/Button";
    import { withStyles } from "@material-ui/core/styles";
    
    const Button = withStyles({
      root: {
        "&.Mui-disabled": {
          pointerEvents: "auto"
        }
      }
    })(MuiButton);
    

    The first problem can be addressed by using Button's component prop to use a <div> element instead of a <button> element. Disabled button elements do not receive click events, so in order to have the Button still behave in a disabled fashion, this code removes the onClick prop when it is disabled.

      const ButtonWithTooltip = ({ tooltipText, disabled, onClick, ...other }) => {
        const adjustedButtonProps = {
          disabled: disabled,
          component: disabled ? "div" : undefined,
          onClick: disabled ? undefined : onClick
        };
        return (
          <Tooltip title={tooltipText}>
            <Button {...other} {...adjustedButtonProps} />
          </Tooltip>
        );
      };
    

    Below is a working demonstration with Buttons B and C both disabled. Buttons A and B use the approach outlined above and Button C is a regular Material-UI Button without a Tooltip for comparison. The additional Button below them toggles the disabled state of B and C.

    import React from "react";
    import Tooltip from "@material-ui/core/Tooltip";
    import MuiButton from "@material-ui/core/Button";
    import { withStyles } from "@material-ui/core/styles";
    import ButtonGroup from "@material-ui/core/ButtonGroup";
    
    const Button = withStyles({
      root: {
        "&.Mui-disabled": {
          pointerEvents: "auto"
        }
      }
    })(MuiButton);
    
    const ButtonWithTooltip = ({ tooltipText, disabled, onClick, ...other }) => {
      const adjustedButtonProps = {
        disabled: disabled,
        component: disabled ? "div" : undefined,
        onClick: disabled ? undefined : onClick
      };
      return (
        <Tooltip title={tooltipText}>
          <Button {...other} {...adjustedButtonProps} />
        </Tooltip>
      );
    };
    
    export default function App() {
      const [bAndCDisabled, setBAndCDisabled] = React.useState(true);
    
      return (
        <>
          <ButtonGroup>
            <ButtonWithTooltip
              tooltipText="This is Button A"
              onClick={() => console.log("A")}
            >
              {"Button A"}
            </ButtonWithTooltip>
            <ButtonWithTooltip
              tooltipText="This is Button B"
              onClick={() => console.log("B")}
              disabled={bAndCDisabled}
            >
              {"Button B"}
            </ButtonWithTooltip>
            <MuiButton onClick={() => console.log("C")} disabled={bAndCDisabled}>
              {"Button C"}
            </MuiButton>
          </ButtonGroup>
          <br />
          <br />
          <MuiButton
            variant="contained"
            onClick={() => setBAndCDisabled(!bAndCDisabled)}
          >
            Toggle disabled for B and C
          </MuiButton>
        </>
      );
    }
    

    Edit ButtonGroup with Tooltip on disabled Button