Skip to main content
The Loader component displays a full-screen loading screen with the Music Store branding and a spinning animation. It automatically fades out after a delay and triggers a callback when complete.

Overview

Key features:
  • Full-screen overlay with black background
  • Large “MUSIC STORE” title in Bebas Neue font
  • Spinning loader animation
  • GSAP-powered fade-out transition
  • Callback support for post-load actions
  • High z-index for top-layer rendering

Props

onComplete
function
required
Callback function triggered when the loader fade-out animation completes

Implementation

import { useEffect, useRef } from "react";
import gsap from "gsap";

function Loader({ onComplete }) {
  const loaderRef = useRef(null);

  useEffect(() => {
    const tl = gsap.timeline();

    tl.to(loaderRef.current, {
      opacity: 0,
      duration: 0.8,
      delay: 1.2,
      ease: "power2.out",
      onComplete
    });
  }, []);

  return (
    <div
      ref={loaderRef}
      className="fixed inset-0 z-[9999] bg-black flex flex-col items-center justify-center gap-4"
    >
      <p
        className="text-white text-5xl font-bold"
        style={{ 
          fontFamily: "'Bebas Neue', sans-serif", 
          letterSpacing: "0.1em" 
        }}
      >
        MUSIC STORE
      </p>
      <div className="w-8 h-8 border-2 border-white/20 border-t-white rounded-full animate-spin" />
    </div>
  );
}

export default Loader;

Usage

Basic Usage

import { useState } from 'react';
import Loader from './components/Loader/Loader';

function App() {
  const [loading, setLoading] = useState(true);

  return (
    <>
      {loading && <Loader onComplete={() => setLoading(false)} />}
      {!loading && (
        <div>
          {/* Your app content */}
        </div>
      )}
    </>
  );
}

With Additional Actions

import { useState } from 'react';
import Loader from './components/Loader/Loader';

function App() {
  const [loading, setLoading] = useState(true);

  const handleLoadComplete = () => {
    // Perform additional actions
    console.log('Loading complete');
    localStorage.setItem('hasSeenLoader', 'true');
    
    // Hide loader
    setLoading(false);
  };

  return (
    <>
      {loading && <Loader onComplete={handleLoadComplete} />}
      {!loading && <YourApp />}
    </>
  );
}

Conditional Loading

import { useState, useEffect } from 'react';
import Loader from './components/Loader/Loader';

function App() {
  const [loading, setLoading] = useState(true);
  const [dataReady, setDataReady] = useState(false);

  useEffect(() => {
    // Fetch initial data
    fetchData().then(() => setDataReady(true));
  }, []);

  const handleLoadComplete = () => {
    if (dataReady) {
      setLoading(false);
    }
  };

  return (
    <>
      {loading && <Loader onComplete={handleLoadComplete} />}
      {!loading && <YourApp />}
    </>
  );
}

Animation Timeline

The loader uses a simple GSAP timeline:
gsap.timeline().to(loaderRef.current, {
  opacity: 0,        // Fade to transparent
  duration: 0.8,     // 800ms fade duration
  delay: 1.2,        // Wait 1200ms before starting fade
  ease: "power2.out", // Ease out for smooth deceleration
  onComplete         // Trigger callback when done
});
Total visible time: 2 seconds (1.2s delay + 0.8s fade)

Styling Breakdown

Container

className="fixed inset-0 z-[9999] bg-black flex flex-col items-center justify-center gap-4"
  • fixed inset-0: Full-screen fixed positioning
  • z-[9999]: Very high z-index to overlay everything
  • bg-black: Solid black background
  • flex flex-col: Vertical flexbox layout
  • items-center justify-center: Center content
  • gap-4: 1rem gap between title and spinner

Title

className="text-white text-5xl font-bold"
style={{ 
  fontFamily: "'Bebas Neue', sans-serif", 
  letterSpacing: "0.1em" 
}}
  • Large (3rem) white text
  • Bebas Neue font for brand consistency
  • Wide letter spacing (0.1em) for dramatic effect

Spinner

className="w-8 h-8 border-2 border-white/20 border-t-white rounded-full animate-spin"
  • 8x8 (2rem) circular element
  • 2px border with 20% white opacity
  • Top border fully white for contrast
  • animate-spin: Tailwind’s built-in spin animation

Spinner Animation

The spinner uses Tailwind’s animate-spin utility, which applies:
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.animate-spin {
  animation: spin 1s linear infinite;
}

Font Requirements

The component requires the Bebas Neue font. Include it in your HTML:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap" rel="stylesheet">
Or in your CSS:
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap');

Customization

Change Duration

function Loader({ onComplete }) {
  useEffect(() => {
    gsap.timeline().to(loaderRef.current, {
      opacity: 0,
      duration: 1.5,  // Slower fade
      delay: 2.0,     // Longer display time
      ease: "power2.out",
      onComplete
    });
  }, []);
  // ...
}

Change Easing

ease: "expo.out"    // More dramatic
ease: "elastic.out"  // Bouncy
ease: "back.out"     // Slight overshoot

Change Text

<p className="text-white text-5xl font-bold">
  YOUR BRAND NAME
</p>

Change Colors

// Container
className="... bg-gradient-to-br from-purple-900 to-black ..."

// Spinner
className="... border-purple-500/20 border-t-purple-500 ..."

Best Practices

State Management

Always use state to conditionally render the loader and hide it after completion

Callback Required

Always provide an onComplete callback to hide the loader

Data Loading

Combine loader visibility with actual data fetching completion

Accessibility

Consider adding aria-label="Loading" and role="status" for screen readers

Accessibility Enhancement

For better accessibility:
<div
  ref={loaderRef}
  className="..."
  role="status"
  aria-label="Loading Music Store"
>
  <p className="..." aria-hidden="true">
    MUSIC STORE
  </p>
  <div className="..." aria-hidden="true" />
  <span className="sr-only">Loading, please wait...</span>
</div>
The loader should only be displayed once per session. Consider using localStorage to track if the user has already seen it.

Build docs developers (and LLMs) love