Compact View Icon
Six dots in a 3×2 grid that reorder via Motion layout animation when active, signaling a compact view mode.
Installation
File Structure
compact-view-icon.tsx
Usage
import { useState } from "react";
import { CompactViewIcon } from "@/components/unlumen-ui/compact-view-icon";
export default function Example() {
const [isActive, setIsActive] = useState(false);
return (
<button onClick={() => setIsActive((prev) => !prev)}>
<CompactViewIcon isActive={isActive} />
</button>
);
}API Reference
CompactViewIcon
isActiveboolean—When true, the six dots reorder from [0,1,2,3,4,5] to [1,2,5,0,3,4] via Motion layout animation.
className?string—Extra classes applied to the root <span> element.
Notes
- Uses Motion
layoutprop to animate each dot to its new grid position. - The active permutation groups the middle and right columns first, then the left column — giving a visual sense of density change.
- Each dot has a staggered delay of
0.018 s × indexfor a flowing 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.
compact-view-icon.tsx
"use client";
import { motion } from "motion/react";
const COMPACT_DELAY_STEP = 0.018;
// Active permutation: B C F / A D E (indices in a 3×2 grid 0-5)
const ACTIVE_ORDER = [1, 2, 5, 0, 3, 4];
const DEFAULT_ORDER = [0, 1, 2, 3, 4, 5];
export interface CompactViewIconProps {
/** Whether the icon is in its active (animated) state. */
isActive: boolean;
/** Extra classes applied to the root element. */
className?: string;
}
export function CompactViewIcon({ isActive, className }: CompactViewIconProps) {
const order = isActive ? ACTIVE_ORDER : DEFAULT_ORDER;
return (
<motion.span
className={`inline-grid h-4 w-4 place-content-center grid-cols-3 gap-[2px] ${className ?? ""}`}
aria-hidden="true"
>
{order.map((id, i) => (
<motion.span
key={id}
layout
className="size-[3px] rounded-[1px] bg-current"
animate={isActive ? { opacity: [0.65, 1, 1] } : { opacity: 1 }}
transition={{
duration: 0.42,
delay: i === 0 ? 0 : i * COMPACT_DELAY_STEP,
ease: "easeOut",
layout: {
duration: 0.45,
delay: i === 0 ? 0 : i * COMPACT_DELAY_STEP,
ease: "easeInOut",
},
}}
/>
))}
</motion.span>
);
}