Stackbits
Buttons > Expandable Icon Button

🏹 Expandable Icon Button

Think of this as the Clark Kent of buttons—small, subtle, and all business at first. But the moment you hover, BOOM! 💥 It expands, revealing its secret identity (a text label!). Perfect for minimalist UIs, modern dashboards, or interactive call-to-actions, this button keeps things sleek while adding a touch of surprise and delight.

Preview

Let's connect!

Follow below steps 👇🏻

Install dependencies

1
npm i framer-motion

Component

Create a file expandable-icon-button.tsx in your components folder and paste this code

1
'use client';
2
3
import { motion, useAnimationControls } from 'framer-motion';
4
import { MouseEventHandler, useEffect, useRef, useState } from 'react';
5
6
type ExpandableIconButtonProps = {
7
onClick?: MouseEventHandler<HTMLButtonElement>;
8
icon: React.ReactNode;
9
text: string;
10
className?: string;
11
};
12
13
const ExpandableIconButton = ({ onClick, icon, text, className }: ExpandableIconButtonProps) => {
14
const buttonControls = useAnimationControls();
15
const textControls = useAnimationControls();
16
17
const textRef = useRef<HTMLParagraphElement>(null);
18
const [textWidth, setTextWidth] = useState<number>(0);
19
const iconRef = useRef<HTMLDivElement>(null);
20
const [iconWidth, setIconWidth] = useState<number>(0);
21
22
useEffect(() => {
23
if (iconRef.current) {
24
setIconWidth(iconRef.current.offsetWidth);
25
}
26
if (textRef.current) {
27
setTextWidth(textRef.current.offsetWidth);
28
}
29
}, [iconRef, textRef]);
30
31
const onMouseEnter = () => {
32
buttonControls.start({ width: iconWidth + textWidth + 40 });
33
textControls.start('visible');
34
};
35
36
const onMouseLeave = () => {
37
buttonControls.start({ width: iconWidth + 32 });
38
textControls.start('hide');
39
};
40
41
return (
42
<motion.button
43
onClick={onClick}
44
animate={buttonControls}
45
transition={{ duration: 0.4, ease: 'backOut' }}
46
onMouseEnter={onMouseEnter}
47
onMouseLeave={onMouseLeave}
48
style={{
49
gridTemplateColumns: 'auto 1fr',
50
width: iconWidth + 32,
51
opacity: iconWidth <= 0 ? 0 : 1
52
}}
53
className={`grid grid-cols-2 items-center p-[16px] justify-start gap-[8px] rounded-full overflow-hidden ${className}`}
54
>
55
<div ref={iconRef} className="p-0 flex items-center justify-center">
56
{icon}
57
</div>
58
<motion.p
59
ref={textRef}
60
initial={{ opacity: 0, scale: 0 }}
61
variants={{
62
visible: { opacity: 1, scale: 1 },
63
hide: { opacity: 0, scale: 0 }
64
}}
65
animate={textControls}
66
>
67
{text}
68
</motion.p>
69
</motion.button>
70
);
71
};
72
73
export default ExpandableIconButton;

Usage

1
<ExpandableIconButton
2
onClick={() => (window.location.href = 'https://twitter.com/samitkapoorr')}
3
icon={<TwitterIcon />}
4
text={'samitkapoorr'}
5
className="border-[1px] hover:bg-blue-500"
6
/>

⭐️ Got a question or feedback?
Feel free to reach out!