Components / Artist List
Artist List
An interactive artist showcase component with hover-activated profiles. Features smooth spring animations, social links, and a responsive layout that adapts beautifully across all screen sizes.
Inspired by Vercel Ship 2025
Preview
Usage
tsx
import { useArtist } from "@/hooks/use-artist"
import { ArtistList } from "@/components/vercel-speaker/artist-list"
import { Loader } from "lucide-react"
export default function ArtistPage() {
const { artists, loading } = useArtist()
if (loading) {
return (
<div className="flex h-64 items-center justify-center">
<Loader className="animate-spin text-4xl text-zinc-400" />
</div>
)
}
return (
<div className="overflow-hidden border border-zinc-800">
<ArtistList artists={artists} />
</div>
)
}Features
- •Hover interactions — Artist profiles reveal on hover with smooth transitions
- •Spring animations — Bouncy, natural-feeling motion using Motion library
- •Mobile responsive — Profile image moves to top on smaller screens with tap support
- •Social links — Animated icon wobble effect on hover
Props
| Component | Prop | Type | Description |
|---|---|---|---|
| ArtistList | artists | Artist[] | Array of artist objects |
| ArtistMember | artist | Artist | Individual artist data |
| ArtistMember | isActive | boolean | Whether this artist is selected |
| ArtistMember | onHover | () => void | Callback when hovered/clicked |
| ArtistProfile | artist | Artist | Artist to display profile image |
Artist Type
The Artist interface defines the shape of each artist object.
artist.tstypescript
import type { StaticImageData } from 'next/image'
export interface Artist {
id: string
name: string
image: string | StaticImageData
album: string
social: {
twitter?: string
instagram?: string
}
}Dependencies
This component requires the following packages:
bash
pnpm add motion lucide-react react-iconsSource Code
This component requires 6 files. Copy them into your components/vercel-speaker/ folder and the hook into hooks/.
tsx
'use client'
import { type Artist } from "./artist"
import { useState } from "react"
import { ArtistMember } from "./artist-member"
import { ArtistProfile } from "./artist-profile"
interface ArtistListProps {
artists: Artist[]
}
export function ArtistList({ artists }: ArtistListProps) {
const [activeArtist, setActiveArtist] = useState<Artist | null>(artists[0] || null)
const handleHover = (artist: Artist) => {
setActiveArtist(artist)
}
return (
<div className="flex flex-col md:flex-row md:items-stretch md:divide-x divide-zinc-800">
{/* Mobile: Show active artist profile at top */}
<div className="relative aspect-[16/9] md:hidden">
{activeArtist && <ArtistProfile artist={activeArtist} />}
</div>
{/* Artist list */}
<div className="w-full md:w-3/5 min-w-0 divide-y divide-zinc-800">
{artists.map((artist) => (
<ArtistMember
key={artist.id}
artist={artist}
isActive={activeArtist?.id === artist.id}
onHover={() => handleHover(artist)}
/>
))}
</div>
{/* Desktop: Show active artist profile on right */}
<div className="relative hidden md:block md:w-2/5 flex-shrink-0 min-h-[400px]">
{activeArtist && <ArtistProfile artist={activeArtist} />}
</div>
</div>
)
}