Blog / CSS

Create Modern Loaders Without JavaScript

Discover 5 types of modern and elegant CSS loaders: spinners, dots, skeleton, progress bars and circular loaders. Complete code and interactive demos included.

Introduction

Loaders are essential for a good user experience. They indicate that something is happening in the background and maintain user engagement during loading times.

In this tutorial, we'll explore 5 types of loaders you can create using only CSS, without any JavaScript dependency. Each example is functional and ready to use in your projects.

💡
Why pure CSS?

CSS loaders are more performant because they are handled by the GPU. They work even if JavaScript is disabled and don't block the main thread.

1. Classic animated spinner

The spinner is the most recognizable loader. Simple but effective, it uses a partial border and continuous rotation to create the illusion of loading.

spinner.css
/* Spinner basique */
.spinner {
  width: 48px;
  height: 48px;
  border: 4px solid rgba(99, 102, 241, 0.2);
  border-top-color: #6366f1;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Dual border spinner */
.spinner-dual {
  width: 48px;
  height: 48px;
  border: 4px solid transparent;
  border-top-color: #6366f1;
  border-bottom-color: #8b5cf6;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

/* Spinner with gradient (advanced technique) */
.spinner-gradient {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: conic-gradient(
    from 0deg, transparent, #6366f1
  );
  mask: radial-gradient(
    farthest-side,
    transparent calc(100% - 4px),
    black calc(100% - 4px)
  );
  animation: spin 1s linear infinite;
}

Customization

  • Size: Modify width and height
  • Thickness: Adjust the border value
  • Speed: Change the animation duration (e.g. 0.5s for faster)

2. Dots loader (bounce)

The dots loader is perfect for a more playful style. The dots bounce with a slight time offset, creating a hypnotic wave effect.

dots-loader.css
/* Dots container */
.dots-loader {
  display: flex;
  gap: 8px;
}

/* Dot style */
.dots-loader .dot {
  width: 12px;
  height: 12px;
  background: #6366f1;
  border-radius: 50%;
  animation: bounce 1.4s ease-in-out infinite both;
}

/* Time offset for each dot */
.dots-loader .dot:nth-child(1) { animation-delay: -0.32s; }
.dots-loader .dot:nth-child(2) { animation-delay: -0.16s; }
.dots-loader .dot:nth-child(3) { animation-delay: 0s; }

@keyframes bounce {
  0%, 80%, 100% {
    transform: scale(0.6);
    opacity: 0.5;
  }
  40% {
    transform: scale(1);
    opacity: 1;
  }
}

/* Variant: Wave bars */
.dots-wave {
  display: flex;
  gap: 6px;
  align-items: flex-end;
  height: 40px;
}

.dots-wave .bar {
  width: 8px;
  background: #6366f1;
  border-radius: 4px;
  animation: wave 1.2s ease-in-out infinite;
}

@keyframes wave {
  0%, 100% { height: 10px; }
  50% { height: 40px; }
}

3. Skeleton loader with shimmer

The skeleton loader is ideal for content that takes time to load. It displays a silhouette of the final content with a shimmer effect that crosses the element.

skeleton.css
/* Skeleton base with shimmer effect */
.skeleton {
  background: linear-gradient(
    90deg,
    #1a1a25 25%,
    #252532 50%,
    #1a1a25 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 8px;
}

@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

/* Skeleton elements */
.skeleton-avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
}

.skeleton-title {
  height: 20px;
  width: 70%;
  margin-bottom: 12px;
}

.skeleton-text {
  height: 14px;
  width: 100%;
  margin-bottom: 8px;
}

.skeleton-text:last-child {
  width: 80%;
}
🛠
Pro tip

Create your skeletons to match exactly the final layout. This reduces the "layout shift" and improves Core Web Vitals.

4. Animated progress bar

Progress bars are perfect when you know the advancement or to show continuous activity. Here are three different styles.

progress-bar.css
/* Progress bar animee */
.progress-bar {
  height: 8px;
  background: rgba(99, 102, 241, 0.2);
  border-radius: 4px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
  border-radius: 4px;
  animation: progress 2s ease-in-out infinite;
}

@keyframes progress {
  0% { width: 0%; }
  50% { width: 100%; }
  100% { width: 0%; }
}

/* Progress indeterminee */
.progress-indeterminate {
  height: 8px;
  background: rgba(99, 102, 241, 0.2);
  border-radius: 4px;
  overflow: hidden;
  position: relative;
}

.progress-indeterminate::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 40%;
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
  border-radius: 4px;
  animation: indeterminate 1.5s ease-in-out infinite;
}

@keyframes indeterminate {
  0% { left: -40%; }
  100% { left: 100%; }
}

/* Progress rayee animee */
.progress-striped-fill {
  background: repeating-linear-gradient(
    45deg,
    #6366f1,
    #6366f1 10px,
    #8b5cf6 10px,
    #8b5cf6 20px
  );
  background-size: 28.28px 100%;
  animation: stripes 1s linear infinite;
}

@keyframes stripes {
  0% { background-position: 0 0; }
  100% { background-position: 28.28px 0; }
}

5. Circular loader with percentage

The circular loader with percentage is perfect for uploads or long processes. It combines SVG and CSS for an elegant and informative result.

0%
circular-progress.html
<!-- HTML Structure -->
<div class="circular-progress">
  <svg width="100" height="100" viewBox="0 0 100 100">
    <circle class="bg" cx="50" cy="50" r="45"/>
    <circle class="progress" cx="50" cy="50" r="45"/>
  </svg>
  <span class="percentage">75%</span>
</div>
circular-progress.css
.circular-progress {
  position: relative;
  width: 100px;
  height: 100px;
}

.circular-progress svg {
  transform: rotate(-90deg);
}

.circular-progress circle {
  fill: none;
  stroke-width: 8;
  stroke-linecap: round;
}

.circular-progress .bg {
  stroke: rgba(99, 102, 241, 0.2);
}

.circular-progress .progress {
  stroke: #6366f1;
  /* Circonference = 2 * PI * r = 2 * 3.14159 * 45 = 283 */
  stroke-dasharray: 283;
  /* For 75%: 283 - (283 * 0.75) = 70.75 */
  stroke-dashoffset: 70.75;
  transition: stroke-dashoffset 0.5s ease;
}

.circular-progress .percentage {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 1.25rem;
  font-weight: 700;
}

/* Loop animation */
@keyframes circularProgress {
  0% { stroke-dashoffset: 283; }
  50% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 283; }
}

Best practices and accessibility

Creating aesthetic loaders is good, but making them accessible is better. Here are the essential points to follow.

Performance

  • Prefer transform and opacity for animations (GPU accelerated)
  • Avoid animating width, height or margin (they trigger reflow)
  • Use will-change sparingly for complex animations

Accessibility

  • Add role="status" to the loader container
  • Use aria-label to describe the current action
  • Respect prefers-reduced-motion for sensitive users
accessibility.css
/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
  .spinner,
  .dots-loader .dot,
  .skeleton,
  .progress-fill,
  .circular-progress .progress {
    animation: none;
  }

  /* Static alternative for the spinner */
  .spinner {
    border-color: #6366f1;
    opacity: 0.7;
  }
}

/* Accessible HTML */
<!-- Example -->
<div role="status" aria-label="Loading">
  <div class="spinner"></div>
  <span class="sr-only">Loading...</span>
</div>

/* Text visible only to screen readers */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
Watch out for infinite animations

Looping animations can be problematic for people with epilepsy or vestibular disorders. Always provide an alternative with prefers-reduced-motion.

Conclusion

You now have 5 types of modern and performant CSS loaders. These components are lightweight, accessible and fully customizable according to your visual identity.

Don't hesitate to combine these techniques and experiment with colors, timings and easings to create unique loaders that perfectly match your brand.

🎨
Go further

Find over 50 loaders ready to use in our loaders library, with one-click copyable code and built-in customizer.