Pure TailwindMIT licensed

Tailwind Glass Bento Grid Layout

A liquid-glass bento over a slowly drifting conic mesh, where each tile sets its own refraction with an inline --edge — 0 for flat frosted panels, 1 for a pronounced lensed rim. The animation lives in @theme as --animate-tcg15-bg, which is how v4 mints an animate-* utility from a keyframe.

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="tcg-15 w-full min-h-svh p-5.5 font-tcg15-sans bg-[conic-gradient(from_20deg_at_40%_30%,#7c3aed,#2563eb,#0891b2,#7c3aed)] bg-[length:200%_200%] animate-tcg15-bg motion-reduce:animate-none" aria-label="Liquid glass bento grid demo">
  <div class="max-w-[640px] mx-auto grid grid-cols-4 max-[520px]:grid-cols-2 [grid-auto-rows:96px] gap-3">

    <a href="#system" style="--edge:1" class="relative isolate col-span-2 row-span-2 flex flex-col justify-end p-4 rounded-[20px] overflow-hidden no-underline text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/32 backdrop-blur-[18px] backdrop-saturate-180 shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)] transition-transform duration-300 hover:-translate-y-[3px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:translate-y-0 after:absolute after:inset-0 after:rounded-[20px] after:pointer-events-none after:opacity-[var(--edge,0)] after:shadow-[inset_0_0_calc(var(--edge,0)*26px)_calc(var(--edge,0)*6px)_oklch(1_0_0/0.35)]">
      <span class="mb-auto text-[11px] font-extrabold tracking-[0.1em] uppercase opacity-80">Featured</span>
      <h3 class="mt-2 text-2xl font-extrabold leading-[1.2]">Design system v3</h3>
      <p class="mt-1.5 text-[12.5px] text-white/82 text-pretty">72 components shipped this quarter with full Liquid Glass theming.</p>
    </a>

    <a href="#refraction" style="--edge:.6" class="relative isolate col-span-2 row-span-2 flex flex-col justify-end p-4 rounded-[20px] overflow-hidden no-underline text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/32 backdrop-blur-[18px] backdrop-saturate-180 shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)] transition-transform duration-300 hover:-translate-y-[3px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:translate-y-0 after:absolute after:inset-0 after:rounded-[20px] after:pointer-events-none after:opacity-[var(--edge,0)] after:shadow-[inset_0_0_calc(var(--edge,0)*26px)_calc(var(--edge,0)*6px)_oklch(1_0_0/0.35)]">
      <span class="mb-auto text-[11px] font-extrabold tracking-[0.1em] uppercase opacity-80">Guide</span>
      <h3 class="mt-2 text-lg font-extrabold leading-[1.2]">Refraction 101</h3>
    </a>

    <a href="#demos" style="--edge:0" class="relative isolate flex flex-col justify-end p-4 rounded-[20px] overflow-hidden no-underline text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/32 backdrop-blur-[18px] backdrop-saturate-180 shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)] transition-transform duration-300 hover:-translate-y-[3px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
      <span class="text-4xl font-black leading-none tabular-nums">21</span>
      <p class="mt-1.5 text-[12.5px] text-white/82">Demos</p>
    </a>

    <a href="#cls" style="--edge:0" class="relative isolate flex flex-col justify-end p-4 rounded-[20px] overflow-hidden no-underline text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/32 backdrop-blur-[18px] backdrop-saturate-180 shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)] transition-transform duration-300 hover:-translate-y-[3px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
      <span class="text-4xl font-black leading-none tabular-nums">0</span>
      <p class="mt-1.5 text-[12.5px] text-white/82">CLS score</p>
    </a>

    <a href="#perf" style="--edge:.4" class="relative isolate col-span-2 flex flex-col justify-end p-4 rounded-[20px] overflow-hidden no-underline text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/32 backdrop-blur-[18px] backdrop-saturate-180 shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)] transition-transform duration-300 hover:-translate-y-[3px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:translate-y-0 after:absolute after:inset-0 after:rounded-[20px] after:pointer-events-none after:opacity-[var(--edge,0)] after:shadow-[inset_0_0_calc(var(--edge,0)*26px)_calc(var(--edge,0)*6px)_oklch(1_0_0/0.35)]">
      <span class="mb-auto text-[11px] font-extrabold tracking-[0.1em] uppercase opacity-80">Perf</span>
      <h3 class="mt-2 text-lg font-extrabold leading-[1.2]">60fps, GPU-composited</h3>
    </a>

  </div>
</section>
@import "tailwindcss";

@theme {
  --font-tcg15-sans: 'Space Grotesk', system-ui, sans-serif;
  --animate-tcg15-bg: tcg15-bg 24s ease-in-out infinite;
}
/* Keyframes cannot be a utility, so the body stays here; the @theme entry above
   is what mints the animate-tcg15-bg utility that references it. */

@keyframes tcg15-bg {
  50% {
    background-position: 100% 100%;
  }
}
/* Root rule: the full-bleed contract. */

.tcg-15 {
  width: 100%;
  min-height: 100svh;
}

.tcg-15 ::selection {
  background: #fff;
  color: #2563eb;
}

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-tcg15-sans: 'Space Grotesk', system-ui, sans-serif;
  --animate-tcg15-bg: tcg15-bg 24s ease-in-out infinite;
}
/* Keyframes cannot be a utility, so the body stays here; the @theme entry above
   is what mints the animate-tcg15-bg utility that references it. */

@keyframes tcg15-bg {
  50% {
    background-position: 100% 100%;
  }
}
/* Root rule: the full-bleed contract. */

.tcg-15 {
  width: 100%;
  min-height: 100svh;
}

.tcg-15 ::selection {
  background: #fff;
  color: #2563eb;
}
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=Space+Grotesk:wght@400;500;600;700;800&display=swap">

What changed in the port

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

  • The @keyframes body has to stay in CSS — there is no utility form — but the animation shorthand becomes --animate-tcg15-bg in @theme, which mints animate-tcg15-bg. That split is the correct v4 idiom for any custom animation.
  • The glass material is six utilities that only mean anything together, which is a good argument for keeping them adjacent on the element rather than hidden behind a class name that says nothing about them.
  • --edge stays inline and is read twice inside one calc() in the rim's inset shadow — the pattern the hamburger collection uses for per-item indices, applied to refraction.
  • Tiles gained a loud focus-visible:outline-3 outline-white: on glass a subtle ring is genuinely invisible, so the accessible answer here is a strong one.
  • motion-reduce:animate-none stops the 24s mesh drift; the gradient stays, so the design is intact without the movement.
  • This is the collection's narrowest-support demo, and the port does not change that — backdrop-filter and color-mix() set the floor, not Tailwind.
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 Glass Bento Grid Layout
Source: https://codefronts.com/layouts/tailwind-card-grid-layout/tailwind-glass-bento-grid-layout/

A liquid-glass bento over a slowly drifting conic mesh, where each tile sets its own refraction with an inline --edge &mdash; 0 for flat frosted panels, 1 for a pronounced lensed rim. The animation lives in @theme as --animate-tcg15-bg, which is how v4 mints an animate-* utility from a keyframe.
## HTML
```html
<section class="tcg-15 w-full min-h-svh p-5.5 font-tcg15-sans bg-[conic-gradient(from_20deg_at_40%_30%,#7c3aed,#2563eb,#0891b2,#7c3aed)] bg-[length:200%_200%] animate-tcg15-bg motion-reduce:animate-none" aria-label="Liquid glass bento grid demo">
  <div class="max-w-[640px] mx-auto grid grid-cols-4 max-[520px]:grid-cols-2 [grid-auto-rows:96px] gap-3">

    <a href="#system" style="--edge:1" class="relative isolate col-span-2 row-span-2 flex flex-col justify-end p-4 rounded-[20px] overflow-hidden no-underline text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/32 backdrop-blur-[18px] backdrop-saturate-180 shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)] transition-transform duration-300 hover:-translate-y-[3px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:translate-y-0 after:absolute after:inset-0 after:rounded-[20px] after:pointer-events-none after:opacity-[var(--edge,0)] after:shadow-[inset_0_0_calc(var(--edge,0)*26px)_calc(var(--edge,0)*6px)_oklch(1_0_0/0.35)]">
      <span class="mb-auto text-[11px] font-extrabold tracking-[0.1em] uppercase opacity-80">Featured</span>
      <h3 class="mt-2 text-2xl font-extrabold leading-[1.2]">Design system v3</h3>
      <p class="mt-1.5 text-[12.5px] text-white/82 text-pretty">72 components shipped this quarter with full Liquid Glass theming.</p>
    </a>

    <a href="#refraction" style="--edge:.6" class="relative isolate col-span-2 row-span-2 flex flex-col justify-end p-4 rounded-[20px] overflow-hidden no-underline text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/32 backdrop-blur-[18px] backdrop-saturate-180 shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)] transition-transform duration-300 hover:-translate-y-[3px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:translate-y-0 after:absolute after:inset-0 after:rounded-[20px] after:pointer-events-none after:opacity-[var(--edge,0)] after:shadow-[inset_0_0_calc(var(--edge,0)*26px)_calc(var(--edge,0)*6px)_oklch(1_0_0/0.35)]">
      <span class="mb-auto text-[11px] font-extrabold tracking-[0.1em] uppercase opacity-80">Guide</span>
      <h3 class="mt-2 text-lg font-extrabold leading-[1.2]">Refraction 101</h3>
    </a>

    <a href="#demos" style="--edge:0" class="relative isolate flex flex-col justify-end p-4 rounded-[20px] overflow-hidden no-underline text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/32 backdrop-blur-[18px] backdrop-saturate-180 shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)] transition-transform duration-300 hover:-translate-y-[3px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
      <span class="text-4xl font-black leading-none tabular-nums">21</span>
      <p class="mt-1.5 text-[12.5px] text-white/82">Demos</p>
    </a>

    <a href="#cls" style="--edge:0" class="relative isolate flex flex-col justify-end p-4 rounded-[20px] overflow-hidden no-underline text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/32 backdrop-blur-[18px] backdrop-saturate-180 shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)] transition-transform duration-300 hover:-translate-y-[3px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
      <span class="text-4xl font-black leading-none tabular-nums">0</span>
      <p class="mt-1.5 text-[12.5px] text-white/82">CLS score</p>
    </a>

    <a href="#perf" style="--edge:.4" class="relative isolate col-span-2 flex flex-col justify-end p-4 rounded-[20px] overflow-hidden no-underline text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/32 backdrop-blur-[18px] backdrop-saturate-180 shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)] transition-transform duration-300 hover:-translate-y-[3px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:translate-y-0 after:absolute after:inset-0 after:rounded-[20px] after:pointer-events-none after:opacity-[var(--edge,0)] after:shadow-[inset_0_0_calc(var(--edge,0)*26px)_calc(var(--edge,0)*6px)_oklch(1_0_0/0.35)]">
      <span class="mb-auto text-[11px] font-extrabold tracking-[0.1em] uppercase opacity-80">Perf</span>
      <h3 class="mt-2 text-lg font-extrabold leading-[1.2]">60fps, GPU-composited</h3>
    </a>

  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --font-tcg15-sans: 'Space Grotesk', system-ui, sans-serif;
  --animate-tcg15-bg: tcg15-bg 24s ease-in-out infinite;
}
/* Keyframes cannot be a utility, so the body stays here; the @theme entry above
   is what mints the animate-tcg15-bg utility that references it. */

@keyframes tcg15-bg {
  50% {
    background-position: 100% 100%;
  }
}
/* Root rule: the full-bleed contract. */

.tcg-15 {
  width: 100%;
  min-height: 100svh;
}

.tcg-15 ::selection {
  background: #fff;
  color: #2563eb;
}
```

How this works

Glass is a composition, so it is a class group. Fill, border, inner highlight, drop shadow, blur and saturation are six declarations that only mean anything together. Tailwind expresses all six as utilities, so the tile carries its own material:

class="bg-[color-mix(in_oklab,white_12%,transparent)]
  border border-white/32 backdrop-blur-[18px] backdrop-saturate-180
  shadow-[inset_0_1px_0_oklch(1_0_0/0.45),0_20px_44px_-28px_oklch(0_0_0/0.6)]"

Refraction is per-tile data. --edge stays inline because it is an instance value, and the after: rim reads it twice inside a calc() — blur radius and spread both scale, so one number takes a tile from flat frosted to heavily lensed.

The keyframe becomes a token. @keyframes cannot be a utility, but --animate-tcg15-bg: tcg15-bg 24s ease-in-out infinite can: v4 mints animate-tcg15-bg from it, so the canvas states its own animation and the stylesheet holds only the keyframe body.

Every tile is a link with a real focus ring. focus-visible:outline-3 focus-visible:outline-white — on glass, a subtle ring is invisible, so this one is deliberately loud.

Make it yours

  • Set any tile's refraction with its inline --edge: 0 is flat frosted, 1 is a heavy lens.
  • Rearrange the bento with col-span-* / row-span-* on the tiles.
  • Swap the conic mesh for imagery (bg-[url(…)] bg-cover) — the lensing reads far more strongly over photography.
  • Slow the drift by editing --animate-tcg15-bg's duration in @theme.
  • Tune the frost through backdrop-blur-[18px] and the white_12% mix percentage; they trade off against each other.

Gotchas — read before shipping

  • Glass needs texture behind it. Over a flat colour the whole material collapses into a slightly lighter rectangle and the rim disappears.
  • backdrop-blur is expensive. Nine or ten blurred tiles is fine; a wall of fifty will cost frames on low-end hardware.
  • Keep the grid gap generous, or adjacent lensed rims visually collide and read as a seam.
  • White text on glass over a mid-tone mesh is a contrast risk; the tiles here keep an inner highlight and a dark drop shadow specifically to hold the type legible.
  • color-mix() and backdrop-filter are the narrow parts of this demo's support — a browser without them shows a plain translucent panel, which is an acceptable degradation but not the design.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

backdrop-filter needs Chrome 76+ / Safari 14+ with prefix / Firefox 103+, and color-mix() is narrower still; the lensed rim degrades to a simple bright border. This is the narrowest-support demo in the collection.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

Plain CSSno Tailwind surface

Search CodeFronts

Loading…