Clip Corners Button
Clip Corners Button is a React and Tailwind CSS button component from SmoothUI, licensed MIT. Copy it and paste it into your project.
SmoothUI
MIT
buttons
What it is
Clip Corners Button is a React and Tailwind CSS button component from SmoothUI, licensed MIT. Copy it and paste it into your project.
How to use it
Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.
Five free copies a month. No card.
Needs
npm i motion
Licence
This component comes from SmoothUI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.
Clip Corners Button
"use client";
import SmoothButton from "@/components/smoothui/smooth-button";
import { motion, useReducedMotion } from "motion/react";
import { useEffect, useState } from "react";
export interface ClipCornersButtonProps {
children: React.ReactNode;
className?: string;
onClick?: () => void;
}
export function ClipCornersButton({
children,
className = "",
onClick,
}: ClipCornersButtonProps) {
const [isHovered, setIsHovered] = useState(false);
const shouldReduceMotion = useReducedMotion();
const [isHoverDevice, setIsHoverDevice] = useState(false);
// Distance triangles move on hover
const move = -4;
useEffect(() => {
const mediaQuery = window.matchMedia("(hover: hover) and (pointer: fine)");
setIsHoverDevice(mediaQuery.matches);
const handleChange = (e: MediaQueryListEvent) => {
setIsHoverDevice(e.matches);
};
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
}, []);
return (
<SmoothButton
className={`relative overflow-hidden rounded-none border-none bg-foreground px-8 py-4 font-mono text-2xl text-background hover:bg-foreground/90 ${className}`}
onClick={onClick}
onMouseEnter={() => {
if (isHoverDevice) {
setIsHovered(true);
}
}}
onMouseLeave={() => setIsHovered(false)}
style={{ borderRadius: 8 }}
type="button"
>
{/* Top-left triangle */}
<motion.div
animate={
shouldReduceMotion || !isHoverDevice
? {}
: {
x: isHovered ? -move : 0,
y: isHovered ? -move : 0,
}
}
className="absolute top-1.5 left-1.5"
initial={false}
style={{ height: 8, width: 8 }}
transition={
shouldReduceMotion
? { duration: 0 }
: {
damping: 24,
duration: 0.2,
stiffness: 400,
type: "spring" as const,
}
}
>
<svg
aria-label="Top-left triangle"
className="fill-background"
height="8"
width="8"
>
<title>Top-left triangle</title>
<polygon fill="currentColor" points="0,0 8,0 0,8" />
</svg>
</motion.div>
{/* Top-right triangle */}
<motion.div
animate={
shouldReduceMotion || !isHoverDevice
? {}
: {
x: isHovered ? move : 0,
y: isHovered ? -move : 0,
}
}
className="absolute top-1.5 right-1.5"
initial={false}
style={{ height: 8, width: 8 }}
transition={
shouldReduceMotion
? { duration: 0 }
: {
damping: 24,
duration: 0.2,
stiffness: 400,
type: "spring" as const,
}
}
>
<svg
aria-label="Top-right triangle"
className="fill-background"
height="8"
width="8"
>
<title>Top-right triangle</title>
<polygon fill="currentColor" points="8,0 8,8 0,0" />
</svg>
</motion.div>
{/* Bottom-left triangle */}
<motion.div
animate={
shouldReduceMotion || !isHoverDevice
? {}
: {
x: isHovered ? -move : 0,
y: isHovered ? move : 0,
}
}
className="absolute bottom-1.5 left-1.5"
initial={false}
style={{ height: 8, width: 8 }}
transition={
shouldReduceMotion
? { duration: 0 }
: {
damping: 24,
duration: 0.2,
stiffness: 400,
type: "spring" as const,
}
}
>
<svg
aria-label="Bottom-left triangle"
className="fill-background"
height="8"
width="8"
>
<title>Bottom-left triangle</title>
<polygon fill="currentColor" points="0,8 8,8 0,0" />
</svg>
</motion.div>
{/* Bottom-right triangle */}
<motion.div
animate={
shouldReduceMotion || !isHoverDevice
? {}
: {
x: isHovered ? move : 0,
y: isHovered ? move : 0,
}
}
className="absolute right-1.5 bottom-1.5"
initial={false}
style={{ height: 8, width: 8 }}
transition={
shouldReduceMotion
? { duration: 0 }
: {
damping: 24,
duration: 0.2,
stiffness: 400,
type: "spring" as const,
}
}
>
<svg
aria-label="Bottom-right triangle"
className="fill-background"
height="8"
width="8"
>
<title>Bottom-right triangle</title>
<polygon fill="currentColor" points="8,8 0,8 8,0" />
</svg>
</motion.div>
<span className="relative z-10 flex w-full select-none items-center justify-center">
{children}
</span>
</SmoothButton>
);
}
export default ClipCornersButton;