Tailwind v4MIT licensed

Tailwind Liquid Glass Loading Skeleton

A profile-card skeleton where the placeholders are glass rather than grey. One card-wide shimmer sweeps across all of them — a single animated layer instead of one per block, which is both cheaper and the more convincing illusion of light gliding over a surface.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="lgt-19 grid w-full min-h-svh place-items-center p-15 font-lgt19-sans bg-[image:url('https://picsum.photos/seed/lgskel/1100/800')] bg-cover bg-center bg-fixed" aria-label="Tailwind liquid glass loading skeleton">
  <div aria-busy="true" class="relative isolate w-[min(360px,92vw)] overflow-hidden rounded-[24px] border border-white/24 bg-white/9 p-6.5 backdrop-blur-[20px] backdrop-saturate-160 shadow-[inset_0_1px_0_rgba(255,255,255,.35),0_30px_60px_-30px_rgba(0,0,0,.6)] after:pointer-events-none after:absolute after:inset-0 after:z-2 after:animate-lgt19-sweep after:[background:linear-gradient(105deg,transparent_30%,rgba(255,255,255,.28)_48%,transparent_66%)] motion-reduce:after:animate-none motion-reduce:after:opacity-15">
    <span class="sr-only">Loading profile&hellip;</span>

    <div class="mb-5.5 flex items-center gap-4">
      <div class="size-15 flex-none rounded-full border border-white/15 bg-white/16"></div>
      <div class="flex flex-1 flex-col gap-2.25">
        <div class="h-4 w-[65%] rounded-lg border border-white/15 bg-white/16"></div>
        <div class="h-3 w-2/5 rounded-lg border border-white/15 bg-white/16"></div>
      </div>
    </div>

    <div class="mb-3 h-3 w-full rounded-md border border-white/15 bg-white/16"></div>
    <div class="mb-3 h-3 w-full rounded-md border border-white/15 bg-white/16"></div>
    <div class="mb-3 h-3 w-[55%] rounded-md border border-white/15 bg-white/16"></div>

    <div class="mt-4.5 flex items-center justify-between">
      <div class="h-7 w-21 rounded-full border border-white/15 bg-white/16"></div>
      <div class="h-10 w-27.5 rounded-xl border border-white/15 bg-white/16"></div>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --font-lgt19-sans: 'Manrope', system-ui, sans-serif;
  --animate-lgt19-sweep: lgt-19-sweep 1.6s ease-in-out infinite;
}
/* The hold from 60% to 100% is what makes the sweep breathe instead of strobe. */

@keyframes lgt-19-sweep {
  0% {
    transform: translateX(-100%);
  }

  60%,
    100% {
    transform: translateX(100%);
  }
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --font-lgt19-sans: 'Manrope', system-ui, sans-serif;
  --animate-lgt19-sweep: lgt-19-sweep 1.6s ease-in-out infinite;
}
/* The hold from 60% to 100% is what makes the sweep breathe instead of strobe. */

@keyframes lgt-19-sweep {
  0% {
    transform: translateX(-100%);
  }

  60%,
    100% {
    transform: translateX(100%);
  }
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;800&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • A single card-wide after: shimmer replaces per-placeholder animations — cheaper to paint and a better illusion.
  • Every placeholder shares one glass class string, so the skeleton has the same material vocabulary as the rest of the collection.
  • Placeholder dimensions mirror the real card, which is the whole mechanism behind a zero-CLS swap.
  • aria-busy and an sr-only line give screen-reader users the information the shimmer gives everyone else.
  • Under motion-reduce the sweep freezes at a faint 15% opacity rather than vanishing, so the loading cue survives.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Liquid Glass Loading Skeleton
Source: https://codefronts.com/design-styles/tailwind-liquid-glass-effects/tailwind-liquid-glass-loading-skeleton/

A profile-card skeleton where the placeholders are glass rather than grey. One card-wide shimmer sweeps across all of them &mdash; a single animated layer instead of one per block, which is both cheaper and the more convincing illusion of light gliding over a surface.
## HTML
```html
<section class="lgt-19 grid w-full min-h-svh place-items-center p-15 font-lgt19-sans bg-[image:url('https://picsum.photos/seed/lgskel/1100/800')] bg-cover bg-center bg-fixed" aria-label="Tailwind liquid glass loading skeleton">
  <div aria-busy="true" class="relative isolate w-[min(360px,92vw)] overflow-hidden rounded-[24px] border border-white/24 bg-white/9 p-6.5 backdrop-blur-[20px] backdrop-saturate-160 shadow-[inset_0_1px_0_rgba(255,255,255,.35),0_30px_60px_-30px_rgba(0,0,0,.6)] after:pointer-events-none after:absolute after:inset-0 after:z-2 after:animate-lgt19-sweep after:[background:linear-gradient(105deg,transparent_30%,rgba(255,255,255,.28)_48%,transparent_66%)] motion-reduce:after:animate-none motion-reduce:after:opacity-15">
    <span class="sr-only">Loading profile&hellip;</span>

    <div class="mb-5.5 flex items-center gap-4">
      <div class="size-15 flex-none rounded-full border border-white/15 bg-white/16"></div>
      <div class="flex flex-1 flex-col gap-2.25">
        <div class="h-4 w-[65%] rounded-lg border border-white/15 bg-white/16"></div>
        <div class="h-3 w-2/5 rounded-lg border border-white/15 bg-white/16"></div>
      </div>
    </div>

    <div class="mb-3 h-3 w-full rounded-md border border-white/15 bg-white/16"></div>
    <div class="mb-3 h-3 w-full rounded-md border border-white/15 bg-white/16"></div>
    <div class="mb-3 h-3 w-[55%] rounded-md border border-white/15 bg-white/16"></div>

    <div class="mt-4.5 flex items-center justify-between">
      <div class="h-7 w-21 rounded-full border border-white/15 bg-white/16"></div>
      <div class="h-10 w-27.5 rounded-xl border border-white/15 bg-white/16"></div>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --font-lgt19-sans: 'Manrope', system-ui, sans-serif;
  --animate-lgt19-sweep: lgt-19-sweep 1.6s ease-in-out infinite;
}
/* The hold from 60% to 100% is what makes the sweep breathe instead of strobe. */

@keyframes lgt-19-sweep {
  0% {
    transform: translateX(-100%);
  }

  60%,
    100% {
    transform: translateX(100%);
  }
}
```

How this works

One sweep, not twelve. The naive skeleton animates a gradient on every placeholder, which is a dozen animated layers for one loading state. Here a single after: layer spans the card and translates across everything:

after:absolute after:inset-0 after:z-2 after:pointer-events-none
after:[background:linear-gradient(105deg,transparent_30%,rgba(255,255,255,.28)_48%,transparent_66%)]
after:animate-lgt19-sweep

The keyframe holds at the end. 0% to 60% travels, 60% to 100% rests off-screen. Without that pause the sweep reads as a strobe; with it, it reads as light passing periodically over glass.

Zero CLS is the actual product requirement. Every placeholder matches the dimensions of the element it stands in for, so when real content arrives nothing moves. A skeleton that shifts layout on swap has made the problem worse, not better.

Loading is announced, not just drawn. aria-busy="true" and an sr-only line tell screen-reader users what the shimmer tells everyone else — and both come off when the data lands.

Make it yours

  • Copy your real card's exact box sizes into the placeholders; that is the entire technique for a CLS-free swap.
  • Change the shimmer's speed and angle in the keyframe; 1.6s with a hold is calm, sub-1s starts to feel anxious.
  • Tint the placeholders to your surface colour instead of white for skeletons on light backgrounds.
  • Add or remove lines to mirror richer cards — the single sweep covers whatever is underneath.
  • Wrap it in a component that renders skeleton or content from one prop, so the two layouts cannot drift apart.

Gotchas — read before shipping

  • Mirror the final layout's dimensions or the swap causes layout shift, which is the exact metric a skeleton exists to protect.
  • Use one card-wide shimmer. One animation per placeholder multiplies paint cost for a strictly worse effect.
  • Announce the loading state and remove aria-busy when done — a region permanently marked busy is a bug that only screen-reader users experience.
  • The shimmer needs pointer-events-none, or it will sit over any interactive content you reveal underneath it.
  • overflow-hidden on the card clips the sweep; without it the light beam escapes past the rounded corners.
  • Under motion-reduce keep a faint static highlight rather than nothing — the user still needs to know something is loading.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Utilities only, no JavaScript. Where backdrop-filter is unavailable the placeholders are solid translucent shapes with the same shimmer, and the CLS behaviour is identical.

Techniques used in this demo

Plain CSSno Tailwind surface

Search CodeFronts

Loading…