List View Icon
Three horizontal lines that shuffle vertically when active, animated with Motion spring transitions.
Installation
File Structure
list-view-icon.tsx
Usage
import { useState } from "react";
import { ListViewIcon } from "@/components/unlumen-ui/list-view-icon";
export default function Example() {
const [isActive, setIsActive] = useState(false);
return (
<button onClick={() => setIsActive((prev) => !prev)}>
<ListViewIcon isActive={isActive} />
</button>
);
}API Reference
ListViewIcon
isActiveboolean—When true, the three lines animate to their shuffled positions. When false they return to the resting state.
className?string—Extra classes applied to the root <span> element.
Notes
- The icon is a
<span>(not an SVG) composed of three<span>elements styled as horizontal lines. - Lines 0 and 1 travel downward by 5 px; line 2 travels upward by 10 px, producing a shuffle effect.
- Each line has a staggered delay of
0.02 s × indexfor a ripple feel. - Set
aria-labelon the parent button to describe the action.
Credits
Built by leo.
Keep in mind
Most components on this site are inspired by or recreated from existing work across the web. I'm not here to take credit; just to learn, experiment, and sometimes push things a bit further. If something looks familiar and I forgot to mention you, reach out and I'll fix that right away.
list-view-icon.tsx
"use client";
import { motion } from "motion/react";
const LINE_DELAY_STEP = 0.02;
const LINE_STEP = 5; // 2px line + 3px gap
const LINE_TRAVEL: Record<number, number> = {
0: LINE_STEP,
1: LINE_STEP,
2: -LINE_STEP * 2,
};
export interface ListViewIconProps {
/** Whether the icon is in its active (animated) state. */
isActive: boolean;
/** Extra classes applied to the root element. */
className?: string;
}
export function ListViewIcon({ isActive, className }: ListViewIconProps) {
return (
<motion.span
className={`inline-flex h-4 w-4 flex-col items-center justify-center gap-[3px] ${className ?? ""}`}
aria-hidden="true"
>
{[0, 1, 2].map((i) => (
<motion.span
key={i}
className="h-[2px] w-3 rounded-full bg-current"
animate={
isActive
? { y: LINE_TRAVEL[i], opacity: [0.55, 1, 1] }
: { y: 0, opacity: 0.9 }
}
transition={{
duration: 0.36,
delay: i === 0 ? 0 : i * LINE_DELAY_STEP,
ease: "easeOut",
}}
/>
))}
</motion.span>
);
}