Empty state

The first impression nobody remembers to design

## The starting point ```tsx <div className="mt-12 flex flex-col items-center"> <svg xmlns="http://www.w3.org/2000/svg" className="h-16 w-16 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" > <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-2.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" /> </svg> <h2 className="mt-4 text-lg font-medium text-gray-900">No data yet</h2> <p className="mt-1 text-sm text-gray-500"> There is currently no data to display. Please add some data to get started with the dashboard. </p> <button className="mt-6 rounded bg-blue-600 px-4 py-2 text-sm text-white"> Get Started </button> </div> ``` Count the problems. ## Layer 1: Copywriting ### Fix the heading **Before:** "No data yet" **After:** "Your first deploy is five minutes away" ### Fix the helper text **Before:** "There is currently no data to display. Please add some data to get started with the dashboard." **After:** "Connect a repository and push a commit. Your build logs, deploy previews, and uptime metrics will appear here automatically." ### Fix the CTA **Before:** "Get Started" **After:** "Connect your first repository" ## Layer 2: Typography ### Heading scale `text-lg` is too small for the space it occupies. The heading needs to command the entire content area: ```tsx <h2 className="mt-6 text-xl font-semibold tracking-tight text-foreground sm:text-2xl text-balance"> Your first deploy is five minutes away </h2> ``` ### Body typography ```tsx <p className="mt-3 max-w-md text-sm leading-6 text-muted-foreground text-balance"> Connect a repository and push a commit. Your build logs, deploy previews, and uptime metrics will appear here automatically. </p> ``` ## Layer 3: Craft ### Vertical centring ```tsx <div className="flex flex-1 flex-col items-center justify-center px-6 py-24"> <div className="flex max-w-sm flex-col items-center text-center"> ``` ### Illustration accessibility ```tsx <svg aria-hidden="true" className="size-12 text-muted-foreground/50" ...> ``` ### CTA as a link ```tsx <a href="/setup/connect" className="mt-8 inline-flex h-11 items-center justify-center rounded-lg bg-primary px-6 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90" > Connect your first repository </a> ``` ## Layer 4: Animation ### Stagger entrance ```tsx <motion.svg aria-hidden="true" initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, ease: [0.22, 1, 0.36, 1] }} className="size-12 text-muted-foreground/50" /> <motion.h2 initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.05, ease: [0.22, 1, 0.36, 1] }} > Your first deploy is five minutes away </motion.h2> <motion.p initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.1, ease: [0.22, 1, 0.36, 1] }} > ``` ### Reduced motion ```tsx const prefersReduced = useReducedMotion(); <motion.h2 initial={prefersReduced ? false : { opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} > ``` When `initial` is `false`, the element renders immediately.