I'm trying to change the variant and/or size on a material-ui button for different screen sizes. For example, use no variant or size="small" below the "sm" breakpoint and variant="outlined" and/or size="large" above "sm".
Normally, I'd make use withStyles and create a style with theme.breakpoints to affect changes by applying the style to the element using className, however, variant and size are props.
After reading the api, scouring the web, and fiddling extensively, I can't seem to figure out any straight-forward way to change the props based on viewport width.
I've thought about creating a "width-detector" and then using some JS logic to change the button element's props accordingly, but that seems just a bit far out as a solution.
So I'm asking here to see if there is an easier solution out there. Thanks.
The withWidth
HOC is deprecated per Material UI Docs.
This is the approach that works now, with a combination of useTheme
and useMediaQuery
.
Edit: useTheme
is not really required here, since useMediaQuery
automatically provides that as an argument.
// import { useTheme } from "@material-ui/core/styles";
import { useMediaQuery } from "@material-ui/core";
...
function ResponsiveButton() {
// const theme = useTheme();
// const isSmallScreen = useMediaQuery(theme.breakpoints.down("xs"));
const isSmallScreen = useMediaQuery(theme => theme.breakpoints.down("xs"));
const buttonProps = {
variant: isSmallScreen ? "text" : "outlined",
size: isSmallScreen ? "small" : "large"
};
return (
<Button {...buttonProps} color="primary">
Responsive Button
</Button>
);
}
export default ResponsiveButton;
Material UI Docs:
Sometimes you might want to change the React rendering tree based on the breakpoint value. We provide a withWidth() higher-order component for this use case.
withWidth
injects a width
property into your component that gives you access to the current breakpoint value. This allows you to render different props or content based on screen size.
function ResponsiveButton({ width }) {
// This is equivalent to theme.breakpoints.down("sm")
const isSmallScreen = /xs|sm/.test(width);
const buttonProps = {
variant: isSmallScreen ? "text" : "outlined",
size: isSmallScreen ? "small" : "large"
};
return (
<Button {...buttonProps} color="primary">
Responsive Button
</Button>
);
}
export default withWidth()(ResponsiveButton);
This is my implementation
import Button from "@material-ui/core/Button";
import { useTheme } from "@material-ui/core/styles";
import useMediaQuery from "@material-ui/core/useMediaQuery";
import { useEffect } from "react";
const ResponsiveButton = (props) => {
const theme = useTheme();
const desktop = useMediaQuery(theme.breakpoints.up("lg"));
const tablet = useMediaQuery(theme.breakpoints.up("sm"));
const mobile = useMediaQuery(theme.breakpoints.up("xs"));
const sizes = () => {
if (desktop) return "large";
if (tablet) return "medium";
if (mobile) return "small";
};
return <Button {...props} size={sizes()}/>;
};
export default ResponsiveButton;
To all those who are from 'mui' and not 'material-ui' age, you can create two different components and pass display as an in-line style. Only one will be rendered for a specific screen size.
Refer the example below.
<Typography
sx={{ display:{sx:'none', sm:'none', md:'block', lg:'block', xl:'block'} }}
>
This will be rendered only on screens which are medium-sized and above
</Typography>
<Typography
sx={{ display:{sx:'block', sm:'block', md:'none', lg:'none', xl:'none'} }}
>
This will be rendered only on screens which are below medium-sized
</Typography>
Not sure if you must mention all the sizes inside display. Maybe you can skip lg and xl once you set md to 'block'.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With