← Component Library
Media Component

Animated GIF

A GIF is an image that moves — a short, silent, looping animation. Here's how to add one, when to reach for video instead, how to make and shrink them, and how to keep motion accessible.

01 · The Idea

What a GIF actually is

A GIF (Graphics Interchange Format) packs many frames into one image file that the browser plays automatically and loops forever. It's just an image — so you embed it with a plain <img> tag.

It's an image

Use <img> like any photo — no <video>, no player.

Auto-plays & loops

Starts on load and repeats — you can't pause or scrub it.

Always silent

GIFs carry no audio track at all.

Big & low-color

Limited to 256 colors and large file sizes — the catch.

The looping squares below are a CSS stand-in for a real .gif — they show the “short silent loop” idea without shipping a heavy file.
02 · Embed One

Add a GIF to the page

Exactly like an image: set src, write meaningful alt, and include width/height so the layout doesn't jump while it loads.

Stand-ins for looping GIFs (a bouncing dot and a spinner).

HTML

<img src="loading.gif"
     alt="Loading animation"
     width="130" height="130">

CSS

img { border-radius: 12px; max-width: 100%; height: auto; }
/* A GIF can't be paused or styled internally —
   it just plays. To control playback, use a video instead. */

JavaScript — fake pause/play by swapping a still frame

// A real GIF can't be paused. The classic workaround is to swap
// the .gif file for a still .png (a single paused frame) on click.
//   <img class="gif-toggle"
//        src="loading.gif"
//        data-still="loading-still.png"
//        alt="Loading animation">
document.querySelectorAll('.gif-toggle').forEach(img => {
  const animated = img.src;
  const still = img.dataset.still;
  let playing = true;
  img.style.cursor = 'pointer';
  img.title = 'Click to pause/play';
  img.addEventListener('click', () => {
    playing = !playing;
    img.src = playing ? animated : still;
  });
});
03 · GIF vs Video

When to use a GIF — and when not to

GIFs are fine for tiny, simple loops (a small icon, a reaction). For anything longer or detailed, a muted looping video is much smaller and sharper.

 Animated GIFMuted loop video (mp4/webm)
File sizeLarge (often MBs)5–10× smaller
Colors / qualityMax 256 colorsFull color, crisp
AudioNeverOptional (muted here)
ControlsNone (always loops)Can pause / control
Best forTiny icons, stickersEverything longer
Rule of thumb: more than a second or two of motion? Export a video instead of a GIF — your page will load far faster.
04 · The Video Alternative

A “GIF” that's really a video

To get GIF-like behavior (auto-play, loop, no controls) at a fraction of the size, use a <video> that's muted, loop, autoplay, and playsinline.

HTML

<video autoplay loop muted playsinline width="320">
  <source src="clip.webm" type="video/webm">
  <source src="clip.mp4"  type="video/mp4">
</video>

CSS

video { width: 100%; height: auto; border-radius: 12px; }
05 · Make & Optimize

Create a GIF (and shrink it)

If you do need a real GIF, make it small from the start.

Create

Tools like EZGIF, Giphy, or Photoshop (Export › Save for Web) turn a clip into a GIF.

Shrink dimensions

Keep it small on screen — a 320px-wide GIF is a fraction of a 1080px one.

Fewer colors & frames

Reduce the color count and trim frames/length — the two biggest size wins.

Or convert to video

EZGIF and Cloudinary can turn a GIF straight into .mp4/.webm.

06 · Accessibility

Respect motion preferences

Auto-playing, never-ending motion can be distracting — or harmful for people with vestibular conditions. A real GIF can't be paused, which is another reason to prefer video. When you control the animation (video or CSS), honor the user's reduce motion setting.

CSS

@media (prefers-reduced-motion: reduce) {
  video { /* pause or swap for a still image */ }
  .animated { animation: none; }
}
07 · Full Page

The whole thing as one file

Everything above — the looping GIF-style stand-ins, the muted-loop video alternative, and the reduced-motion rules — combined into one complete HTML file. Copy it into a new file, open it in a browser, and it just works — no libraries, no build step. Here's the live result, then the full code.

Live preview

Complete HTML — copy this whole file

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Animated GIF Loops</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #1f2433; background: #f6f7fb; line-height: 1.6; }

    .wrap { max-width: 760px; margin: 0 auto; padding: 36px 24px 60px; }
    h1 { font-size: 2rem; margin-bottom: 8px; }
    h1 .accent { color: #38bdf8; }
    .lead { color: #5b6473; margin-bottom: 28px; }
    h2 { font-size: 1.2rem; margin: 32px 0 12px; }
    p.note { color: #6b7280; font-size: .9rem; margin-top: 10px; }

    /* ===== GIF-style looping stand-ins (CSS, no heavy .gif file) ===== */
    .gif-stage { display: flex; gap: 18px; flex-wrap: wrap; }
    .gif-box { width: 130px; height: 130px; border-radius: 12px; background: linear-gradient(135deg,#38bdf8,#9b7bff); display: grid; place-items: center; overflow: hidden; }
    .gif-box .ball { width: 28px; height: 28px; border-radius: 50%; background: #fff; animation: bounce 1.1s ease-in-out infinite; }
    @keyframes bounce { 0%,100% { transform: translateY(-30px); } 50% { transform: translateY(30px); } }
    .gif-box .ring { width: 56px; height: 56px; border: 6px solid rgba(255,255,255,.35); border-top-color: #fff; border-radius: 50%; animation: spin 1s linear infinite; }
    @keyframes spin { to { transform: rotate(360deg); } }

    /* ===== The video alternative: GIF-like behavior, far smaller ===== */
    video { width: 320px; max-width: 100%; height: auto; border-radius: 12px; display: block; background: #111; }

    /* A real GIF embeds exactly like a photo */
    img.gif { border-radius: 12px; max-width: 100%; height: auto; }

    /* ===== Respect motion preferences ===== */
    @media (prefers-reduced-motion: reduce) {
      .gif-box .ball, .gif-box .ring { animation: none; }
    }
  </style>
</head>
<body>

  <main class="wrap">
    <h1>Animated <span class="accent">GIF</span> Loops</h1>
    <p class="lead">A GIF is just an image that moves — a short, silent, looping animation embedded with a plain <code>&lt;img&gt;</code> tag.</p>

    <h2>1. Looping GIF stand-ins</h2>
    <div class="gif-stage">
      <div class="gif-box"><div class="ball"></div></div>
      <div class="gif-box"><div class="ring"></div></div>
    </div>
    <p class="note">CSS stand-ins for looping GIFs (a bouncing dot and a spinner) — the “short silent loop” idea without a heavy file.</p>

    <h2>2. Embed a real GIF</h2>
    <img class="gif" src="loading.gif" alt="Loading animation" width="130" height="130">
    <p class="note">Set <code>src</code>, write meaningful <code>alt</code>, and include <code>width</code>/<code>height</code> so the layout doesn't jump.</p>

    <h2>3. The video alternative</h2>
    <video autoplay loop muted playsinline poster="">
      <source src="clip.webm" type="video/webm">
      <source src="clip.mp4" type="video/mp4">
      Your browser can't play this clip.
    </video>
    <p class="note">A <code>&lt;video&gt;</code> that's <code>muted loop autoplay playsinline</code> behaves like a GIF at a fraction of the size — and users can pause it.</p>
  </main>

</body>
</html>
That's a real, standalone page. The live preview above is rendered from the exact code in the box — so what you copy is precisely what you see.
08 · Related

Keep building

See Images, Video, Video & Multimedia, and other Media Patterns — or browse the full Component Library.