Installation
Buttons in Sync UI are built using Material-UI and Framer Motion:npm install @mui/material @emotion/react @emotion/styled framer-motion react-icons
Variants
Neubrutalism
A bold, blocky button style with a distinctive shadow.import { Button, useTheme } from "@mui/material";
import { motion } from "motion/react";
const MotionButton = motion.create(Button);
const NeubrutalismButton = () => {
const theme = useTheme();
return (
<MotionButton
variant="contained"
sx={{
backgroundColor: theme.palette.mode === "dark" ? "#000000" : "#FFFFFF",
color: "text.primary",
border: "1px solid",
borderColor: "divider",
boxShadow: "4px 4px 0 currentColor",
borderRadius: 0,
transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
"&:hover": {
borderColor: "divider",
boxShadow: "2px 2px 0 currentColor",
backgroundColor: theme.palette.mode === "dark" ? "#000000" : "#FFFFFF",
},
}}
>
Neubrutalism
</MotionButton>
);
};
export default NeubrutalismButton;
Animated Border
A button with an animated border effect.import { Button, Box, useTheme, Typography } from "@mui/material";
import { motion } from "motion/react";
const MotionButton = motion.create(Button);
const AnimatedBorderButton = () => {
const theme = useTheme();
return (
<MotionButton
variant="outlined"
sx={{
position: "relative",
overflow: "hidden",
borderRadius: 1,
maxWidth: 120,
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
textAlign: "center",
borderColor: "divider",
"&:hover": {
borderColor: "divider",
outline: 0,
},
}}
>
<Box
sx={{
position: "absolute",
inset: -2,
overflow: "hidden",
borderRadius: "inherit",
padding: 0,
"&::before": {
content: '""',
position: "absolute",
inset: "-200%",
background:
theme.palette.mode === "dark"
? "conic-gradient(from 0deg, transparent 0 340deg, #FFF 360deg)"
: "conic-gradient(from 0deg, transparent 0 340deg, #000 360deg)",
animation: "rotate 3s linear infinite",
},
"@keyframes rotate": {
from: { transform: "rotate(0deg)" },
to: { transform: "rotate(360deg)" },
},
}}
/>
<Box
sx={{
position: "absolute",
inset: 1,
borderRadius: "inherit",
backgroundColor: theme.palette.mode === "dark" ? "#000000" : "#FFFFFF",
}}
/>
<Typography variant="body2" style={{ zIndex: 1, fontWeight: 500 }}>
Border
</Typography>
</MotionButton>
);
};
export default AnimatedBorderButton;
Gradient Shine
A button with a gradient shine effect.import { Button, useTheme } from "@mui/material";
import { motion } from "motion/react";
const MotionButton = motion.create(Button);
const GradientShineButton = () => {
const theme = useTheme();
return (
<MotionButton
variant="contained"
sx={{
background:
theme.palette.mode === "light"
? "linear-gradient(110deg, #fff 45%, #e4e4e7 55%, #fff)"
: "linear-gradient(110deg, #000 45%, #333 55%, #000)",
backgroundSize: "200% 100%",
animation: "shine 2s linear infinite",
color: "text.primary",
border: "1px solid",
borderColor: "divider",
"&:hover": {
borderColor: "divider",
},
"@keyframes shine": {
"0%": { backgroundPosition: "200% 0" },
"100%": { backgroundPosition: "-200% 0" },
},
}}
>
Gradient Shine
</MotionButton>
);
};
export default GradientShineButton;
Underline
A text button with an animated underline effect on hover.import { Button } from "@mui/material";
import { motion } from "motion/react";
const MotionButton = motion.create(Button);
const UnderlineButton = () => {
return (
<MotionButton
variant="text"
sx={{
color: "text.primary",
position: "relative",
"&::after": {
content: '""',
position: "absolute",
width: "100%",
height: "1px",
bottom: 0,
left: 0,
backgroundColor: "text.primary",
transform: "scaleX(0)",
transformOrigin: "bottom right",
transition: "transform 0.25s cubic-bezier(0.4, 0, 0.2, 1)",
},
"&:hover::after": {
transform: "scaleX(1)",
transformOrigin: "bottom left",
},
"&:hover": {
backgroundColor: "transparent",
},
}}
>
Underline
</MotionButton>
);
};
export default UnderlineButton;
Send Icon
A button that transitions from text to an icon on hover.import { Button, useTheme } from "@mui/material";
import { motion } from "motion/react";
import { IoSend } from "react-icons/io5";
const MotionButton = motion.create(Button);
const SendIconButton = () => {
const theme = useTheme();
return (
<MotionButton
variant="outlined"
sx={{
backgroundColor: theme.palette.mode === "dark" ? "#000000" : "#FFFFFF",
color: "text.primary",
borderColor: "divider",
position: "relative",
overflow: "hidden",
minWidth: "120px",
height: "44px",
padding: "10px 20px",
transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
"&:hover": {
backgroundColor: "text.primary",
color: "background.paper",
borderColor: "divider",
},
"& .icon, & .text": {
position: "absolute",
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%",
width: "100%",
transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
},
"& .icon": {
transform: "translateX(-100%)",
},
"& .text": {
fontSize: "1rem",
},
"&:hover .icon": {
transform: "translateX(0)",
},
"&:hover .text": {
transform: "translateX(100%)",
},
}}
>
<span className="icon">
<IoSend size={20} />
</span>
<span className="text">Send</span>
</MotionButton>
);
};
export default SendIconButton;
Expand
A button that expands to reveal an icon on hover.import { useState } from "react";
import { Button, Box } from "@mui/material";
import { motion, AnimatePresence } from "motion/react";
import { RxPlus } from "react-icons/rx";
const MotionButton = motion.create(Button);
const ExpandButton = () => {
const [isExpanded, setIsExpanded] = useState(false);
return (
<MotionButton
variant="outlined"
onMouseEnter={() => setIsExpanded(true)}
onMouseLeave={() => setIsExpanded(false)}
sx={{
display: "flex",
alignItems: "center",
borderColor: "divider",
"&:hover": { borderColor: "divider" },
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
Expand
<AnimatePresence>
{isExpanded && (
<motion.span
initial={{ opacity: 0, width: 0, marginLeft: 0 }}
animate={{ opacity: 1, width: "auto", marginLeft: 8 }}
exit={{ opacity: 0, width: 0, marginLeft: 0 }}
transition={{ duration: 0.15, ease: "easeOut" }}
style={{ display: "inline-flex", overflow: "hidden" }}
>
<RxPlus size={18} />
</motion.span>
)}
</AnimatePresence>
</Box>
</MotionButton>
);
};
export default ExpandButton;
Hover Arrow
A button with an animated arrow that appears on hover.import { useState } from "react";
import { Button, Box } from "@mui/material";
import { motion, AnimatePresence } from "motion/react";
import { RxArrowRight } from "react-icons/rx";
const MotionButton = motion.create(Button);
const HoverArrowButton = () => {
const [isHovered, setIsHovered] = useState(false);
return (
<MotionButton
variant="outlined"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
sx={{
display: "flex",
alignItems: "center",
fontWeight: 500,
borderColor: "divider",
transition: "none",
"&:hover": {
borderColor: "divider",
backgroundColor: "transparent",
outline: 0,
},
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<AnimatePresence>
{isHovered && (
<motion.span
initial={{ opacity: 0, width: 0, marginRight: 0 }}
animate={{ opacity: 1, width: "auto", marginRight: 8 }}
exit={{ opacity: 0, width: 0, marginRight: 0 }}
transition={{ duration: 0.15, ease: "easeOut" }}
style={{ display: "inline-flex", overflow: "hidden" }}
>
<RxArrowRight size={19} />
</motion.span>
)}
</AnimatePresence>
Hover Arrow
<AnimatePresence>
{!isHovered && (
<motion.span
initial={{ opacity: 0, width: 0, marginLeft: 0 }}
animate={{ opacity: 1, width: "auto", marginLeft: 8 }}
exit={{ opacity: 0, width: 0, marginLeft: 0 }}
transition={{ duration: 0.15, ease: "easeOut" }}
style={{ display: "inline-flex", overflow: "hidden" }}
>
<RxArrowRight size={19} />
</motion.span>
)}
</AnimatePresence>
</Box>
</MotionButton>
);
};
export default HoverArrowButton;
Glitch
A button with a glitch effect.import { Button, useTheme } from "@mui/material";
import { motion } from "motion/react";
const MotionButton = motion.create(Button);
const GlitchButton = () => {
const theme = useTheme();
return (
<MotionButton
sx={{
position: "relative",
color: "text.primary",
backgroundColor: theme.palette.mode === "dark" ? "#000" : "#fff",
border: "1px solid",
borderColor: "divider",
overflow: "hidden",
padding: "8px 20px",
transition: "all 0.2s ease",
"&::before, &::after": {
content: '"Glitch"',
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: "inherit",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
"&::before": {
left: "2px",
textShadow: theme.palette.mode === "dark" ? "2px 0 #fff" : "2px 0 #000",
clipPath: "inset(0 100% 0 0)",
animation: "glitch-anim 2.5s infinite linear alternate-reverse",
},
"&::after": {
left: "-2px",
textShadow: theme.palette.mode === "dark" ? "-2px 0 #fff" : "-2px 0 #000",
clipPath: "inset(0 0 0 100%)",
animation: "glitch-anim 2.5s infinite linear alternate-reverse 1.25s",
},
"&:hover": {
color: theme.palette.mode === "dark" ? "#000" : "#fff",
backgroundColor: theme.palette.mode === "dark" ? "#fff" : "#000",
},
}}
>
Glitch
</MotionButton>
);
};
export default GlitchButton;
Outline Fill
A button that fills with color on hover.import { Button } from "@mui/material";
import { motion } from "motion/react";
const MotionButton = motion.create(Button);
const OutlineFillButton = () => {
return (
<MotionButton
variant="outlined"
sx={{
color: "text.primary",
borderColor: "divider",
position: "relative",
overflow: "hidden",
zIndex: 1,
"&::before": {
content: '""',
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor: "text.primary",
transform: "scaleX(0)",
transformOrigin: "right",
transition: "transform 0.25s ease",
zIndex: -1,
},
"&:hover": {
color: "background.paper",
"&::before": {
transform: "scaleX(1)",
transformOrigin: "left",
},
},
}}
>
Fill
</MotionButton>
);
};
export default OutlineFillButton;
Elastic Slide
A button with an elastic sliding effect that reveals an icon.import { Button } from "@mui/material";
import { motion } from "motion/react";
import { RxArrowRight } from "react-icons/rx";
const MotionButton = motion.create(Button);
const ElasticSlideButton = () => {
return (
<MotionButton
variant="outlined"
whileHover="hover"
sx={{
color: "text.primary",
borderColor: "divider",
overflow: "hidden",
"&:hover": {
borderColor: "divider",
backgroundColor: "transparent",
},
}}
>
<motion.div
style={{ display: "flex", alignItems: "center" }}
variants={{
hover: {
x: -5,
transition: {
type: "spring",
stiffness: 500,
damping: 15,
},
},
}}
>
Elastic
<motion.div
style={{ marginLeft: 8, display: "flex", opacity: 0, x: -10 }}
variants={{
hover: {
opacity: 1,
x: 0,
transition: {
type: "spring",
stiffness: 500,
damping: 15,
},
},
}}
>
<RxArrowRight size={19} />
</motion.div>
</motion.div>
</MotionButton>
);
};
export default ElasticSlideButton;
Customization
All button variants can be customized using Material-UI’ssx prop. You can modify:
- Colors using theme palette values
- Sizes by adjusting padding, font size, and icon sizes
- Animation timing and easing functions
- Border styles and shadows
- Hover states and transitions
Dependencies
@mui/material- Material-UI componentsframer-motionormotion/react- Animation libraryreact-icons- Icon library (for variants that use icons)