Intro Loader

A page-load intro that counts to 100 with the real loading while a flipbook of your work plays in a small frame, then grows the last image into its place on the page. It doesn't fade out: its last frame is the page's first.

  • Preloader
  • Hero
  • Image

Uses clsx, tailwind-merge

Open in ChatGPTOpen in Claude
nord.studio
Nord Studio
A dark timber house lit from inside at dusk, behind a eucalyptus tree

We design thingspeople remember

Identity, products and spaces for companies that care how things feel.

Installation

Add the component on its own with the CLI, or copy the file in by hand.

The CLI writes components/ui/intro-loader.tsx and installs clsx and tailwind-merge. It needs the @keyframeui registry in components.json; add it once per project.

npx shadcn@latest add @keyframeui/intro-loader

Examples

Finished sections built with Intro Loader. Each one installs whole, with the component included.

Lookbook

A season label instead of a number, and a tall frame for the looks. The last look lands as the half of the page beside the headline.

maisonlune.com/ss27
Maison Lune
A woman in a pink ruffled dress standing in a dry summer field
Spring / Summer 2027

Summer,undone.

Shop the collection

Wordmark

No images. The wordmark rises a letter at a time as the count runs, then shrinks and flies into the logo's spot in the header. The target can be anything.

verso.capital
Verso

Patient capital forcompanies that makereal things.

  • €420mCommitted since 2019
  • 38Companies backed
  • 11 yrsAverage hold

Props

children

TypeReact.ReactNode

The page, rendered underneath the whole time. Wrap where the last image lands in IntroLoaderTarget, and the lines that rise in after the handoff in IntroLoaderLine (from="above" drops them in, for a nav). useIntroDone() turns true at the handoff, for anything that makes its own entrance.

images

Typestring[]

The flipbook, in order. Each wipes up over the last, a little sooner each time. The last one lands on the target, so make it the image the target shows, at the same address. Four or more good images read best.

mark

TypeReact.ReactNode

Instead of images: a mark that rises letter by letter with the count and flies onto the target, like a wordmark landing in the nav. Set it in the same type as the target. A string is split into letters, kerned as the whole word; anything else rises whole.

brand

TypeReact.ReactNode

Your name or logo, in the loader's top-left corner. It stays put through the handoff, so set it like the logo on the page and in the same spot, and the page's logo takes its place.

counter

Type"bottom-left" | "bottom-right" | "top-left" | "top-right" | falseDefault"bottom-left"

The corner the number sits in, or false for none. The digits are fixed-width and each rolls into place; at 100 the number slides up out of view.

label

TypeReact.ReactNode

Shown in place of the number, like a season. It rises in and leaves at 100 like the number does.

minDuration

TypenumberDefault1.6

Shortest the count can take, in seconds, even when everything has already loaded, so it doesn't flash past on a fast connection. The whole intro runs about 1.3 seconds longer.

maxDuration

TypenumberDefault4

Longest it waits for the fonts, the images and the page's images in view, in seconds, before landing anyway. While it waits, the count creeps on its own, so it never sits at 99.

once

TypebooleanDefaulttrue

Play once per browser session. Reloads, the back button and coming back to the page go straight to it. Arriving by a link to a section (/#work) always does.

storageKey

TypestringDefault"intro-loader"

The sessionStorage key that remembers it played.

skippable

TypebooleanDefaulttrue

A click or any key goes straight to the page. Modifier keys don't, so a screenshot shortcut doesn't skip the moment it's trying to catch.

onComplete

Type() => void

Called as the loader hands off to the page, or straight away when it's skipped.

contained

TypebooleanDefaultfalse

Cover this element instead of the screen, and leave the page's scroll alone. It starts once it's on screen. For a single section, or a preview like the ones here.

overlayClassName

Typestring

Classes for the loader, such as its padding, font and color. Its background is the page's own by default, read from the nearest background behind it, so nothing shows a seam at the handoff.

counterClassName

Typestring

Classes for the number or label, such as its size and font.

frameClassName

TypestringDefault"aspect-[4/5] rounded-sm"

Classes for the center frame the images play in: its width, aspect ratio and corners.

markClassName

Typestring

Classes for the mark, such as its font, weight and size. Match the target's.

className

Typestring

Classes for the element that wraps the page. Other props pass through to it. With reduced motion, there's no loader: the page crossfades in.

nonce

Typestring

Your Content Security Policy's nonce, for the small inline script that puts the loader up before the page is drawn. If the script is blocked, the loader is skipped.