30 JavaScript Image Gallery Examples – Responsive Gallery UI
Futuristic neon JavaScript image gallery interface with glowing thumbnails, lightbox preview, filter controls, zoom buttons and responsive gallery UI panels.

30 JavaScript Image Gallery Examples – Responsive Galleries, Filters & Lightbox UI

HomeBlogJavascript30 JavaScript Image Gallery Examples – Responsive Galleries, Filters & Lightbox UI

JavaScript image galleries are useful interactive UI components for portfolio websites, photography pages, ecommerce product collections, real estate listings, travel blogs, agency case studies, interior design projects, product showcases, event albums, and content-heavy landing pages. A good gallery does more than display images — it helps users browse, filter, preview, search, open, compare, select, and explore visual content in a clear and responsive way.

In this guide, you will find 30 JavaScript image gallery examples for real website projects, including responsive image grids, filterable galleries, searchable galleries, lightbox galleries, keyboard-controlled viewers, masonry galleries, lazy loading galleries, infinite scroll galleries, sortable galleries, favorites, selectable galleries, thumbnail navigation, product image galleries, zoom galleries, before-and-after comparison galleries, drag-and-drop galleries, upload preview galleries, tag-based galleries, paginated galleries, accessible gallery modals, and complete responsive gallery sections.

This post focuses on JavaScript image gallery logic, dynamic rendering, gallery state management, filtering, searching, sorting, image preview handling, modal/lightbox behavior, keyboard navigation, lazy loading, localStorage, drag-and-drop events, responsive gallery layouts, and copy-paste-ready HTML, CSS, and JavaScript examples. For related UI components, you can also explore our JavaScript slider examples, JavaScript search filter examples, JavaScript modal examples, and JavaScript dropdown menu examples.

A JavaScript image gallery is an interactive website component that displays a collection of images and lets users browse them in a more useful way than a static image grid. It can include filters, search, sorting, lightbox previews, keyboard navigation, lazy loading, pagination, favorites, image selection, thumbnail switching, zoom effects, drag-and-drop ordering, or dynamic rendering from a JavaScript data array.

The main purpose of a JavaScript image gallery is to make visual content easier to explore. Instead of placing many images on a page without structure, a gallery can organize images by category, tag, product type, project, location, style, date, or user interaction. This helps visitors find the right image faster and gives the website a more polished, app-like experience.

A JavaScript gallery can be very simple or highly advanced. A basic version may render a responsive grid and open a larger preview when an image is clicked. A more advanced version may support live search, animated filters, keyboard-controlled lightbox navigation, lazy loading, infinite scroll, localStorage favorites, image upload previews, before-and-after comparison sliders, accessible focus trapping, and reusable gallery data structures.

Why Image Galleries Matter

Image galleries matter because visual content is often one of the most important parts of a website. A portfolio page needs to show work clearly. An ecommerce page needs to help users inspect products. A real estate website needs to present rooms and exterior views. A travel blog needs to organize destinations. A case study page needs to show project results. A gallery turns these images into a usable interface instead of a long, unstructured image list.

JavaScript image galleries should be built carefully. A gallery should not only look good, but also load efficiently, work on mobile screens, keep image controls easy to understand, support keyboard users where needed, and avoid heavy scripts that slow down the page. The best gallery examples combine clean UI design with useful JavaScript behavior.

There are many different types of JavaScript image galleries. Some galleries are designed for portfolios, while others are made for ecommerce products, photography albums, blog image collections, real estate listings, dashboards, upload interfaces, comparison sections, or full-screen viewing experiences.

The right image gallery depends on the content and user goal. A product gallery should make image switching and zooming easy. A portfolio gallery should support filtering and project discovery. A photography gallery may need fullscreen viewing and keyboard navigation. A large media archive may need search, tags, sorting, pagination, or lazy loading.

This guide focuses on JavaScript image gallery examples, so every demo is designed around a different JavaScript feature or UI behavior. The examples are not just color variations. Each one uses a different gallery idea, different interaction model, different data structure, different user action, or different image browsing pattern.

A good JavaScript image gallery should be fast, responsive, accessible, easy to browse, and visually clear. Users should quickly understand how to open images, move between images, filter content, search the gallery, return to the grid, and continue browsing without confusion.

Before building a gallery, decide what the user should be able to do with the images. Should they only browse? Should they filter by category? Should they search by keyword? Should they open a lightbox? Should they use keyboard navigation? Should images load only when visible? Should selected images stay saved? Should the gallery render from a reusable data array?

You can combine JavaScript image galleries with many other website UI patterns. Portfolio galleries work well inside modern CSS layouts, product image galleries can be paired with pricing sections or ecommerce cards, visual case studies can use modern hero sections, and gallery contact flows can connect to modern CSS forms.

Now let’s look at 30 JavaScript image gallery examples for real website projects. Each example uses a different gallery layout, JavaScript behavior, user interaction, image browsing pattern, visual style, and responsive approach. You will find dynamic rendering, filters, search, sorting, lightboxes, keyboard navigation, masonry logic, lazy loading, infinite scroll, localStorage favorites, selectable images, thumbnail navigation, product gallery behavior, zoom effects, before-and-after comparison, drag-and-drop ordering, upload previews, tag chips, pagination, accessibility improvements, and a complete responsive gallery system with visible JavaScript, HTML, and CSS code.

1. Responsive Image Grid Gallery

A responsive image grid gallery is a strong starting point for portfolio pages, agency work sections, photography websites, product showcases, blog image collections, and modern landing pages. Instead of hardcoding every image card in HTML, this example uses JavaScript to render the gallery from a reusable image data array.

This makes the gallery easier to maintain because image titles, categories, descriptions, and URLs are stored in one JavaScript array. The layout is fully responsive, the cards have a professional editorial style, and every image is generated dynamically when the page loads.

JavaScript

(function () {
  const grid = document.querySelector("[data-vb-gallery-one-grid]");
  if (!grid) return;

  const galleryItems = [
    {
      title: "Mountain Workspace",
      category: "Workspace",
      description: "A calm desk setup with natural light, mountain view, and clean creative atmosphere.",
      image: "https://picsum.photos/seed/gallery-one-workspace/900/700",
      year: "2026"
    },
    {
      title: "Modern Interior",
      category: "Interior",
      description: "A warm modern room layout with layered materials, soft lighting, and elegant details.",
      image: "https://picsum.photos/seed/gallery-one-interior/900/700",
      year: "2026"
    },
    {
      title: "Urban Architecture",
      category: "Architecture",
      description: "A structured city scene with strong lines, glass surfaces, and modern building forms.",
      image: "https://picsum.photos/seed/gallery-one-architecture/900/700",
      year: "2026"
    },
    {
      title: "Product Detail",
      category: "Product",
      description: "A close-up product composition designed for ecommerce and landing page galleries.",
      image: "https://picsum.photos/seed/gallery-one-product/900/700",
      year: "2026"
    },
    {
      title: "Travel Moment",
      category: "Travel",
      description: "A bright visual story for travel blogs, destination guides, and editorial galleries.",
      image: "https://picsum.photos/seed/gallery-one-travel/900/700",
      year: "2026"
    },
    {
      title: "Creative Studio",
      category: "Studio",
      description: "A professional creative workspace suitable for agency portfolios and case study pages.",
      image: "https://picsum.photos/seed/gallery-one-studio/900/700",
      year: "2026"
    }
  ];

  function createGalleryCard(item, index) {
    return `
      <article class="vb-gallery-one-card">
        <div class="vb-gallery-one-image-wrap">
          <span class="vb-gallery-one-badge">${item.category}</span>
          <img src="${item.image}" alt="${item.title}" loading="lazy">
        </div>

        <div class="vb-gallery-one-body">
          <h4>${item.title}</h4>
          <p>${item.description}</p>

          <div class="vb-gallery-one-meta">
            <small>Image ${String(index + 1).padStart(2, "0")}</small>
            <strong>${item.year}</strong>
          </div>
        </div>
      </article>
    `;
  }

  function renderGallery(items) {
    grid.innerHTML = items.map(createGalleryCard).join("");
  }

  renderGallery(galleryItems);
})();

HTML

<div class="vb-gallery-one-demo">
  <div class="vb-gallery-one-shell">
    <div class="vb-gallery-one-head">
      <span>Example 01</span>
      <h3>Responsive Image Grid Gallery</h3>
      <p>A dynamic JavaScript image gallery that renders all gallery cards from a reusable data array.</p>
    </div>

    <div class="vb-gallery-one-grid" data-vb-gallery-one-grid></div>
  </div>
</div>

CSS

.vb-gallery-one-demo,
.vb-gallery-one-demo * {
  box-sizing: border-box;
}

.vb-gallery-one-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: clamp(26px, 4vw, 42px);
  background:
    radial-gradient(circle at 10% 10%, rgba(14, 165, 233, 0.16), transparent 34%),
    radial-gradient(circle at 90% 12%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #eef2ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-gallery-one-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-one-head {
  display: grid;
  gap: 12px;
  max-width: 780px;
  margin-bottom: 26px;
}

.vb-gallery-one-head span {
  display: inline-flex;
  width: fit-content;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-one-head h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.065em;
}

.vb-gallery-one-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-one-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
}

.vb-gallery-one-card {
  position: relative;
  overflow: hidden;
  min-width: 0;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.26);
  box-shadow: 0 18px 46px rgba(15, 23, 42, 0.10);
  transition: transform 0.24s ease, box-shadow 0.24s ease, border-color 0.24s ease;
}

.vb-gallery-one-card:hover {
  transform: translateY(-6px);
  border-color: rgba(37, 99, 235, 0.34);
  box-shadow: 0 26px 70px rgba(15, 23, 42, 0.16);
}

.vb-gallery-one-image-wrap {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: #e2e8f0;
}

.vb-gallery-one-image-wrap img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.36s ease, filter 0.36s ease;
}

.vb-gallery-one-card:hover .vb-gallery-one-image-wrap img {
  transform: scale(1.08);
  filter: saturate(1.08) contrast(1.04);
}

.vb-gallery-one-badge {
  position: absolute;
  left: 14px;
  top: 14px;
  z-index: 2;
  display: inline-flex;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.74);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  backdrop-filter: blur(10px);
}

.vb-gallery-one-body {
  display: grid;
  gap: 9px;
  padding: 18px;
}

.vb-gallery-one-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 20px !important;
  line-height: 1.18 !important;
  font-weight: 900 !important;
  letter-spacing: -0.035em;
}

.vb-gallery-one-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 600;
}

.vb-gallery-one-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 4px;
  padding-top: 13px;
  border-top: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-gallery-one-meta small {
  color: #2563eb !important;
  -webkit-text-fill-color: #2563eb !important;
  font-size: 12px;
  font-weight: 900;
}

.vb-gallery-one-meta strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 12px;
  font-weight: 900;
}

@media (max-width: 980px) {
  .vb-gallery-one-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .vb-gallery-one-demo {
    padding: 16px;
    border-radius: 26px;
  }

  .vb-gallery-one-head h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-one-grid {
    grid-template-columns: 1fr;
  }

  .vb-gallery-one-card {
    border-radius: 22px;
  }
}

This responsive image grid gallery is useful for portfolio pages, image-heavy landing pages, agency project sections, photography previews, product showcases, and visual blog layouts where the image data should be easy to update from JavaScript.

2. Filterable Image Gallery

A filterable image gallery helps users browse a larger image collection by category. It is useful for portfolios, ecommerce collections, real estate galleries, travel websites, restaurant galleries, interior design projects, event albums, and agency case studies.

This example uses JavaScript category buttons to filter gallery cards by type. The active filter state updates instantly, the result count changes dynamically, and an empty-state message appears when no images match the selected category. The gallery is designed with a dark editorial layout so it looks visually different from the first example.

JavaScript

(function () {
  const grid = document.querySelector("[data-vb-gallery-two-grid]");
  const filters = document.querySelector("[data-vb-gallery-two-filters]");
  const count = document.querySelector("[data-vb-gallery-two-count]");
  const empty = document.querySelector("[data-vb-gallery-two-empty]");

  if (!grid || !filters || !count || !empty) return;

  const galleryItems = [
    {
      title: "Soft Living Room",
      category: "interior",
      label: "Interior",
      description: "A warm interior image for home design, furniture, and architecture websites.",
      image: "https://picsum.photos/seed/gallery-two-interior-one/1000/700"
    },
    {
      title: "Minimal Product Shot",
      category: "product",
      label: "Product",
      description: "A clean product-focused image for ecommerce galleries and landing pages.",
      image: "https://picsum.photos/seed/gallery-two-product-one/1000/700"
    },
    {
      title: "Coastal Escape",
      category: "travel",
      label: "Travel",
      description: "A bright travel image for destination guides, travel blogs, and booking pages.",
      image: "https://picsum.photos/seed/gallery-two-travel-one/1000/700"
    },
    {
      title: "Glass Building Lines",
      category: "architecture",
      label: "Architecture",
      description: "A modern architecture image with strong structure and city atmosphere.",
      image: "https://picsum.photos/seed/gallery-two-architecture-one/1000/700"
    },
    {
      title: "Nordic Kitchen",
      category: "interior",
      label: "Interior",
      description: "A calm interior gallery item for design studios and renovation portfolios.",
      image: "https://picsum.photos/seed/gallery-two-interior-two/1000/700"
    },
    {
      title: "Premium Package",
      category: "product",
      label: "Product",
      description: "A polished product image suitable for brand galleries and shop sections.",
      image: "https://picsum.photos/seed/gallery-two-product-two/1000/700"
    },
    {
      title: "Mountain Route",
      category: "travel",
      label: "Travel",
      description: "A travel story image for adventure websites and editorial image collections.",
      image: "https://picsum.photos/seed/gallery-two-travel-two/1000/700"
    },
    {
      title: "Concrete Facade",
      category: "architecture",
      label: "Architecture",
      description: "A bold architectural gallery image for project showcases and case studies.",
      image: "https://picsum.photos/seed/gallery-two-architecture-two/1000/700"
    }
  ];

  function createCard(item) {
    return `
      <article class="vb-gallery-two-card">
        <img src="${item.image}" alt="${item.title}" loading="lazy">

        <div class="vb-gallery-two-content">
          <span>${item.label}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function updateCount(number) {
    count.textContent = number === 1 ? "1 image" : number + " images";
  }

  function renderGallery(filterValue) {
    const visibleItems = filterValue === "all"
      ? galleryItems
      : galleryItems.filter(function (item) {
          return item.category === filterValue;
        });

    grid.innerHTML = visibleItems.map(createCard).join("");
    updateCount(visibleItems.length);
    empty.hidden = visibleItems.length !== 0;
  }

  filters.addEventListener("click", function (event) {
    const button = event.target.closest("button[data-filter]");
    if (!button) return;

    const filterValue = button.getAttribute("data-filter");

    filters.querySelectorAll("button").forEach(function (filterButton) {
      filterButton.classList.remove("is-active");
    });

    button.classList.add("is-active");
    renderGallery(filterValue);
  });

  renderGallery("all");
})();

HTML

<div class="vb-gallery-two-demo">
  <div class="vb-gallery-two-shell">
    <div class="vb-gallery-two-top">
      <div>
        <span>Example 02</span>
        <h3>Filterable Image Gallery</h3>
        <p>Filter a visual collection by category with JavaScript and update the gallery instantly.</p>
      </div>

      <strong data-vb-gallery-two-count>0 images</strong>
    </div>

    <div class="vb-gallery-two-filters" data-vb-gallery-two-filters>
      <button type="button" class="is-active" data-filter="all">All</button>
      <button type="button" data-filter="interior">Interior</button>
      <button type="button" data-filter="product">Product</button>
      <button type="button" data-filter="travel">Travel</button>
      <button type="button" data-filter="architecture">Architecture</button>
    </div>

    <div class="vb-gallery-two-grid" data-vb-gallery-two-grid></div>

    <div class="vb-gallery-two-empty" data-vb-gallery-two-empty hidden>
      <h4>No images found</h4>
      <p>Try another gallery category to view more images.</p>
    </div>
  </div>
</div>

CSS

.vb-gallery-two-demo,
.vb-gallery-two-demo * {
  box-sizing: border-box;
}

.vb-gallery-two-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 12px 44px 12px 44px;
  background:
    radial-gradient(circle at 12% 16%, rgba(34, 211, 238, 0.18), transparent 34%),
    radial-gradient(circle at 88% 10%, rgba(168, 85, 247, 0.22), transparent 36%),
    linear-gradient(135deg, #020617 0%, #111827 48%, #312e81 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.20);
  box-shadow: 0 28px 90px rgba(2, 6, 23, 0.32);
}

.vb-gallery-two-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-two-top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 22px;
  margin-bottom: 24px;
}

.vb-gallery-two-top span {
  display: inline-flex;
  margin-bottom: 13px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(14, 165, 233, 0.16);
  border: 1px solid rgba(125, 211, 252, 0.24);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-two-top h3 {
  max-width: 720px;
  margin: 0 0 12px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-two-top p {
  max-width: 680px;
  margin: 0 !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-gallery-two-top strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.14);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-two-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 22px;
}

.vb-gallery-two-filters button {
  appearance: none;
  border: 1px solid rgba(148, 163, 184, 0.28);
  border-radius: 999px;
  background: rgba(255,255,255,0.07);
  color: #e2e8f0 !important;
  -webkit-text-fill-color: #e2e8f0 !important;
  padding: 11px 15px;
  font-size: 13px;
  line-height: 1;
  font-weight: 900;
  cursor: pointer;
  transition: transform 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

.vb-gallery-two-filters button:hover {
  transform: translateY(-2px);
  border-color: rgba(34, 211, 238, 0.58);
  background: rgba(34, 211, 238, 0.14);
}

.vb-gallery-two-filters button.is-active {
  border-color: rgba(34, 211, 238, 0.80);
  background: linear-gradient(135deg, #0891b2, #4f46e5);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  box-shadow: 0 14px 34px rgba(79, 70, 229, 0.30);
}

.vb-gallery-two-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-two-card {
  position: relative;
  grid-column: span 4;
  min-height: 310px;
  overflow: hidden;
  border-radius: 26px;
  background: #0f172a;
  border: 1px solid rgba(255,255,255,0.12);
  box-shadow: 0 18px 46px rgba(2, 6, 23, 0.28);
}

.vb-gallery-two-card:nth-child(5n + 1) {
  grid-column: span 5;
}

.vb-gallery-two-card:nth-child(5n + 2) {
  grid-column: span 7;
}

.vb-gallery-two-card img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.38s ease, filter 0.38s ease;
}

.vb-gallery-two-card:hover img {
  transform: scale(1.08);
  filter: saturate(1.12) contrast(1.05);
}

.vb-gallery-two-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(2, 6, 23, 0.04), rgba(2, 6, 23, 0.86)),
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%);
  z-index: 1;
}

.vb-gallery-two-content {
  position: absolute;
  z-index: 2;
  left: 18px;
  right: 18px;
  bottom: 18px;
  display: grid;
  gap: 9px;
}

.vb-gallery-two-content span {
  display: inline-flex;
  width: fit-content;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.66);
  border: 1px solid rgba(255,255,255,0.16);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  backdrop-filter: blur(10px);
}

.vb-gallery-two-content h4 {
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(22px, 3vw, 32px) !important;
  line-height: 1.02 !important;
  font-weight: 950 !important;
  letter-spacing: -0.055em;
}

.vb-gallery-two-content p {
  max-width: 560px;
  margin: 0 !important;
  color: #e2e8f0 !important;
  -webkit-text-fill-color: #e2e8f0 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-gallery-two-empty {
  margin-top: 18px;
  padding: 24px;
  border-radius: 24px;
  background: rgba(255,255,255,0.08);
  border: 1px dashed rgba(148, 163, 184, 0.38);
  text-align: center;
}

.vb-gallery-two-empty h4 {
  margin: 0 0 8px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px !important;
  font-weight: 950 !important;
}

.vb-gallery-two-empty p {
  margin: 0 !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 15px;
  line-height: 1.6;
}

@media (max-width: 980px) {
  .vb-gallery-two-top {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-two-card,
  .vb-gallery-two-card:nth-child(5n + 1),
  .vb-gallery-two-card:nth-child(5n + 2) {
    grid-column: span 6;
  }
}

@media (max-width: 640px) {
  .vb-gallery-two-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-two-top h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-two-filters {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-two-filters button {
    width: 100%;
  }

  .vb-gallery-two-card,
  .vb-gallery-two-card:nth-child(5n + 1),
  .vb-gallery-two-card:nth-child(5n + 2) {
    grid-column: span 12;
    min-height: 280px;
  }
}

This filterable image gallery is useful for portfolio websites, product collections, interior design projects, travel galleries, architecture showcases, restaurant image sections, event albums, and agency case studies where users need to browse images by category.

3. Searchable Image Gallery

A searchable image gallery is useful when a website has many images and users need to find specific visuals quickly. This pattern works well for portfolio archives, product collections, real estate galleries, travel galleries, content libraries, design inspiration pages, and media-heavy websites.

This example uses JavaScript live search logic. Users can search by image title, category, location, or tag. The gallery updates instantly while typing, the visible result counter changes dynamically, and a clear empty state appears when no images match the search query.

JavaScript

(function () {
  const searchInput = document.querySelector("[data-vb-gallery-three-search]");
  const clearButton = document.querySelector("[data-vb-gallery-three-clear]");
  const grid = document.querySelector("[data-vb-gallery-three-grid]");
  const count = document.querySelector("[data-vb-gallery-three-count]");
  const empty = document.querySelector("[data-vb-gallery-three-empty]");

  if (!searchInput || !clearButton || !grid || !count || !empty) return;

  const galleryItems = [
    {
      title: "City Glass Tower",
      category: "Architecture",
      location: "New York",
      tags: ["city", "building", "glass"],
      image: "https://picsum.photos/seed/gallery-three-city/900/900"
    },
    {
      title: "Green Product Box",
      category: "Product",
      location: "Studio",
      tags: ["product", "package", "brand"],
      image: "https://picsum.photos/seed/gallery-three-product/900/900"
    },
    {
      title: "Forest Cabin",
      category: "Nature",
      location: "Finland",
      tags: ["nature", "forest", "cabin"],
      image: "https://picsum.photos/seed/gallery-three-forest/900/900"
    },
    {
      title: "Minimal Kitchen",
      category: "Interior",
      location: "Stockholm",
      tags: ["interior", "kitchen", "minimal"],
      image: "https://picsum.photos/seed/gallery-three-kitchen/900/900"
    },
    {
      title: "Desert Road",
      category: "Travel",
      location: "Arizona",
      tags: ["travel", "road", "desert"],
      image: "https://picsum.photos/seed/gallery-three-road/900/900"
    },
    {
      title: "Creative Desk",
      category: "Workspace",
      location: "Berlin",
      tags: ["workspace", "desk", "creative"],
      image: "https://picsum.photos/seed/gallery-three-desk/900/900"
    },
    {
      title: "Hotel Lounge",
      category: "Interior",
      location: "Paris",
      tags: ["hotel", "lounge", "design"],
      image: "https://picsum.photos/seed/gallery-three-lounge/900/900"
    },
    {
      title: "Coastal View",
      category: "Travel",
      location: "Portugal",
      tags: ["coast", "ocean", "travel"],
      image: "https://picsum.photos/seed/gallery-three-coast/900/900"
    }
  ];

  function createSearchText(item) {
    return [
      item.title,
      item.category,
      item.location,
      item.tags.join(" ")
    ].join(" ").toLowerCase();
  }

  function createCard(item) {
    return `
      <article class="vb-gallery-three-card">
        <div class="vb-gallery-three-image">
          <img src="${item.image}" alt="${item.title}" loading="lazy">
          <span>${item.category}</span>
        </div>

        <div class="vb-gallery-three-body">
          <h4>${item.title}</h4>
          <p>${item.location}</p>

          <div class="vb-gallery-three-tags">
            ${item.tags.map(function (tag) {
              return `<small>#${tag}</small>`;
            }).join("")}
          </div>
        </div>
      </article>
    `;
  }

  function updateCount(number) {
    count.textContent = number === 1 ? "1 result" : number + " results";
  }

  function renderGallery(query) {
    const normalizedQuery = query.trim().toLowerCase();

    const visibleItems = galleryItems.filter(function (item) {
      return createSearchText(item).includes(normalizedQuery);
    });

    grid.innerHTML = visibleItems.map(createCard).join("");
    updateCount(visibleItems.length);
    empty.hidden = visibleItems.length !== 0;
  }

  searchInput.addEventListener("input", function () {
    renderGallery(searchInput.value);
  });

  clearButton.addEventListener("click", function () {
    searchInput.value = "";
    searchInput.focus();
    renderGallery("");
  });

  renderGallery("");
})();

HTML

<div class="vb-gallery-three-demo">
  <div class="vb-gallery-three-shell">
    <div class="vb-gallery-three-hero">
      <div class="vb-gallery-three-copy">
        <span>Example 03</span>
        <h3>Searchable Image Gallery</h3>
        <p>Search a visual gallery by title, category, location, and tags using vanilla JavaScript.</p>
      </div>

      <div class="vb-gallery-three-search-card">
        <label for="vb-gallery-three-search">Search gallery</label>
        <div class="vb-gallery-three-search-wrap">
          <input id="vb-gallery-three-search" type="search" placeholder="Try product, city, nature, interior..." data-vb-gallery-three-search>
          <button type="button" data-vb-gallery-three-clear>Clear</button>
        </div>
        <strong data-vb-gallery-three-count>0 results</strong>
      </div>
    </div>

    <div class="vb-gallery-three-grid" data-vb-gallery-three-grid></div>

    <div class="vb-gallery-three-empty" data-vb-gallery-three-empty hidden>
      <strong>No matching images</strong>
      <p>Try a different keyword, category, location, or tag.</p>
    </div>
  </div>
</div>

CSS

.vb-gallery-three-demo,
.vb-gallery-three-demo * {
  box-sizing: border-box;
}

.vb-gallery-three-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 14% 16%, rgba(16, 185, 129, 0.16), transparent 34%),
    radial-gradient(circle at 88% 12%, rgba(14, 165, 233, 0.18), transparent 36%),
    linear-gradient(135deg, #ecfdf5 0%, #f0f9ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(20, 184, 166, 0.24);
  box-shadow: 0 28px 80px rgba(15, 118, 110, 0.11);
}

.vb-gallery-three-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-three-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 430px);
  gap: 22px;
  align-items: stretch;
  margin-bottom: 24px;
}

.vb-gallery-three-copy {
  min-width: 0;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.08);
}

.vb-gallery-three-copy span {
  display: inline-flex;
  margin-bottom: 15px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ccfbf1;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-three-copy h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-three-copy p {
  max-width: 700px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-three-search-card {
  display: grid;
  align-content: center;
  gap: 13px;
  min-width: 0;
  padding: clamp(20px, 3vw, 28px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 18% 12%, rgba(255,255,255,0.20), transparent 34%),
    linear-gradient(135deg, #0f766e, #0284c7) !important;
  box-shadow: 0 22px 64px rgba(14, 116, 144, 0.22);
}

.vb-gallery-three-search-card label {
  color: #ccfbf1 !important;
  -webkit-text-fill-color: #ccfbf1 !important;
  font-size: 13px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-gallery-three-search-wrap {
  display: flex;
  gap: 10px;
  padding: 8px;
  border-radius: 20px;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.20);
}

.vb-gallery-three-search-wrap input {
  min-width: 0;
  flex: 1;
  border: 0;
  outline: 0;
  border-radius: 15px;
  background: #ffffff;
  color: #0f172a;
  padding: 13px 14px;
  font-size: 15px;
  font-weight: 700;
}

.vb-gallery-three-search-wrap input::placeholder {
  color: #94a3b8;
}

.vb-gallery-three-search-wrap button {
  appearance: none;
  border: 0;
  border-radius: 15px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 12px 15px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-gallery-three-search-card strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 900;
}

.vb-gallery-three-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-three-card {
  min-width: 0;
  overflow: hidden;
  border-radius: 24px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 16px 42px rgba(15, 23, 42, 0.09);
}

.vb-gallery-three-image {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: #e2e8f0;
}

.vb-gallery-three-image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.36s ease;
}

.vb-gallery-three-card:hover img {
  transform: scale(1.08);
}

.vb-gallery-three-image span {
  position: absolute;
  left: 12px;
  bottom: 12px;
  display: inline-flex;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.72);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  backdrop-filter: blur(10px);
}

.vb-gallery-three-body {
  display: grid;
  gap: 8px;
  padding: 15px;
}

.vb-gallery-three-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px !important;
  line-height: 1.15 !important;
  font-weight: 900 !important;
  letter-spacing: -0.035em;
}

.vb-gallery-three-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-gallery-three-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 3px;
}

.vb-gallery-three-tags small {
  display: inline-flex;
  padding: 5px 8px;
  border-radius: 999px;
  background: #f1f5f9;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 11px;
  font-weight: 850;
}

.vb-gallery-three-empty {
  margin-top: 18px;
  padding: 26px;
  border-radius: 24px;
  background: #ffffff;
  border: 1px dashed rgba(20, 184, 166, 0.45);
  text-align: center;
}

.vb-gallery-three-empty strong {
  display: block;
  margin-bottom: 8px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 22px;
  font-weight: 950;
}

.vb-gallery-three-empty p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 15px;
  line-height: 1.6;
}

@media (max-width: 1050px) {
  .vb-gallery-three-hero {
    grid-template-columns: 1fr;
  }

  .vb-gallery-three-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 760px) {
  .vb-gallery-three-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-three-search-wrap {
    flex-direction: column;
  }

  .vb-gallery-three-search-wrap button {
    width: 100%;
  }
}

@media (max-width: 520px) {
  .vb-gallery-three-demo {
    padding: 16px;
    border-radius: 26px;
  }

  .vb-gallery-three-copy h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-three-grid {
    grid-template-columns: 1fr;
  }
}

This searchable image gallery is useful for media libraries, portfolio archives, product image collections, travel galleries, real estate image sections, content directories, and any visual page where users need to quickly find images by keyword.

4. Lightbox Image Gallery

A lightbox image gallery lets users open a larger image preview without leaving the page. It is useful for photography websites, product galleries, portfolio pages, case studies, real estate listings, event albums, restaurant galleries, and visual landing pages.

This example uses JavaScript to open a custom lightbox modal when an image card is clicked. The selected image, title, category, and description are injected into the lightbox dynamically. Users can close the lightbox by clicking the close button, clicking the dark backdrop, or pressing the Escape key.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-four]");
  if (!gallery) return;

  const cards = gallery.querySelectorAll(".vb-gallery-four-card");
  const lightbox = gallery.querySelector("[data-vb-gallery-four-lightbox]");
  const closeButtons = gallery.querySelectorAll("[data-vb-gallery-four-close]");
  const lightboxImage = gallery.querySelector("[data-vb-gallery-four-image]");
  const lightboxTitle = gallery.querySelector("[data-vb-gallery-four-title]");
  const lightboxCategory = gallery.querySelector("[data-vb-gallery-four-category]");
  const lightboxDescription = gallery.querySelector("[data-vb-gallery-four-description]");

  let lastFocusedCard = null;

  function openLightbox(card) {
    lastFocusedCard = card;

    lightboxImage.src = card.getAttribute("data-image");
    lightboxImage.alt = card.getAttribute("data-title");
    lightboxTitle.textContent = card.getAttribute("data-title");
    lightboxCategory.textContent = card.getAttribute("data-category");
    lightboxDescription.textContent = card.getAttribute("data-description");

    lightbox.hidden = false;
    document.body.style.overflow = "hidden";
  }

  function closeLightbox() {
    lightbox.hidden = true;
    lightboxImage.src = "";
    document.body.style.overflow = "";

    if (lastFocusedCard) {
      lastFocusedCard.focus();
    }
  }

  cards.forEach(function (card) {
    card.addEventListener("click", function () {
      openLightbox(card);
    });
  });

  closeButtons.forEach(function (button) {
    button.addEventListener("click", closeLightbox);
  });

  document.addEventListener("keydown", function (event) {
    if (event.key === "Escape" && !lightbox.hidden) {
      closeLightbox();
    }
  });
})();

HTML

<div class="vb-gallery-four-demo">
  <div class="vb-gallery-four-shell" data-vb-gallery-four>
    <div class="vb-gallery-four-top">
      <span>Example 04</span>
      <h3>Lightbox Image Gallery</h3>
      <p>Open large image previews in a custom JavaScript lightbox modal with title, category, and description.</p>
    </div>

    <div class="vb-gallery-four-grid">
      <button type="button" class="vb-gallery-four-card" data-title="Editorial Workspace" data-category="Workspace" data-description="A polished workspace gallery image for agency portfolios and creative websites." data-image="https://picsum.photos/seed/gallery-four-workspace/1200/850">
        <img src="https://picsum.photos/seed/gallery-four-workspace/700/520" alt="Editorial Workspace" loading="lazy">
        <span>Workspace</span>
        <strong>Editorial Workspace</strong>
      </button>

      <button type="button" class="vb-gallery-four-card" data-title="Modern Product Display" data-category="Product" data-description="A clean product gallery preview for ecommerce, SaaS, and product launch pages." data-image="https://picsum.photos/seed/gallery-four-product/1200/850">
        <img src="https://picsum.photos/seed/gallery-four-product/700/520" alt="Modern Product Display" loading="lazy">
        <span>Product</span>
        <strong>Modern Product Display</strong>
      </button>

      <button type="button" class="vb-gallery-four-card" data-title="Hotel Interior Detail" data-category="Interior" data-description="A warm interior image suitable for hospitality, renovation, and design portfolios." data-image="https://picsum.photos/seed/gallery-four-interior/1200/850">
        <img src="https://picsum.photos/seed/gallery-four-interior/700/520" alt="Hotel Interior Detail" loading="lazy">
        <span>Interior</span>
        <strong>Hotel Interior Detail</strong>
      </button>

      <button type="button" class="vb-gallery-four-card" data-title="Mountain Travel Scene" data-category="Travel" data-description="A cinematic travel gallery image for destination pages and adventure blogs." data-image="https://picsum.photos/seed/gallery-four-travel/1200/850">
        <img src="https://picsum.photos/seed/gallery-four-travel/700/520" alt="Mountain Travel Scene" loading="lazy">
        <span>Travel</span>
        <strong>Mountain Travel Scene</strong>
      </button>
    </div>

    <div class="vb-gallery-four-lightbox" data-vb-gallery-four-lightbox hidden>
      <div class="vb-gallery-four-backdrop" data-vb-gallery-four-close></div>

      <div class="vb-gallery-four-modal" role="dialog" aria-modal="true" aria-label="Image preview">
        <button type="button" class="vb-gallery-four-close" data-vb-gallery-four-close>Close</button>

        <div class="vb-gallery-four-preview">
          <img src="" alt="" data-vb-gallery-four-image>
        </div>

        <div class="vb-gallery-four-info">
          <span data-vb-gallery-four-category></span>
          <h4 data-vb-gallery-four-title></h4>
          <p data-vb-gallery-four-description></p>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-gallery-four-demo,
.vb-gallery-four-demo * {
  box-sizing: border-box;
}

.vb-gallery-four-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 18px 56px 18px 56px;
  background:
    radial-gradient(circle at 16% 18%, rgba(244, 63, 94, 0.18), transparent 34%),
    radial-gradient(circle at 86% 14%, rgba(251, 146, 60, 0.20), transparent 34%),
    linear-gradient(135deg, #fff1f2 0%, #fff7ed 52%, #ffffff 100%) !important;
  border: 1px solid rgba(251, 113, 133, 0.24);
  box-shadow: 0 28px 80px rgba(159, 18, 57, 0.10);
}

.vb-gallery-four-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-four-top {
  max-width: 760px;
  margin-bottom: 24px;
}

.vb-gallery-four-top span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ffe4e6;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-four-top h3 {
  margin: 0 0 14px !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-four-top p {
  margin: 0 !important;
  color: #4b5563 !important;
  -webkit-text-fill-color: #4b5563 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-four-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-four-card {
  appearance: none;
  position: relative;
  display: block;
  min-width: 0;
  min-height: 370px;
  overflow: hidden;
  padding: 0;
  border: 0;
  border-radius: 30px;
  background: #111827;
  cursor: pointer;
  text-align: left;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.15);
}

.vb-gallery-four-card:nth-child(even) {
  transform: translateY(24px);
}

.vb-gallery-four-card img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.42s ease, filter 0.42s ease;
}

.vb-gallery-four-card:hover img {
  transform: scale(1.1);
  filter: saturate(1.10) contrast(1.06);
}

.vb-gallery-four-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(17, 24, 39, 0.02), rgba(17, 24, 39, 0.88)),
    radial-gradient(circle at 20% 18%, rgba(255,255,255,0.18), transparent 34%);
}

.vb-gallery-four-card span,
.vb-gallery-four-card strong {
  position: absolute;
  z-index: 2;
  left: 18px;
  right: 18px;
}

.vb-gallery-four-card span {
  bottom: 74px;
  display: inline-flex;
  width: fit-content;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(255,255,255,0.15);
  border: 1px solid rgba(255,255,255,0.18);
  color: #ffe4e6 !important;
  -webkit-text-fill-color: #ffe4e6 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  backdrop-filter: blur(10px);
}

.vb-gallery-four-card strong {
  bottom: 20px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.055em;
}

.vb-gallery-four-lightbox {
  position: fixed;
  inset: 0;
  z-index: 99999;
  display: grid;
  place-items: center;
  padding: clamp(16px, 4vw, 34px);
}

.vb-gallery-four-lightbox[hidden] {
  display: none;
}

.vb-gallery-four-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(2, 6, 23, 0.78);
  backdrop-filter: blur(14px);
}

.vb-gallery-four-modal {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(280px, 0.8fr);
  width: min(1040px, 100%);
  max-height: min(760px, 92vh);
  overflow: hidden;
  border-radius: 34px;
  background: #ffffff;
  box-shadow: 0 30px 100px rgba(2, 6, 23, 0.46);
}

.vb-gallery-four-close {
  position: absolute;
  z-index: 3;
  top: 16px;
  right: 16px;
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.82);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 10px 13px;
  font-size: 12px;
  font-weight: 950;
  cursor: pointer;
}

.vb-gallery-four-preview {
  min-height: 520px;
  background: #111827;
}

.vb-gallery-four-preview img {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 520px;
  object-fit: cover;
}

.vb-gallery-four-info {
  display: grid;
  align-content: center;
  gap: 13px;
  padding: clamp(24px, 4vw, 36px);
}

.vb-gallery-four-info span {
  display: inline-flex;
  width: fit-content;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ffe4e6;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-gallery-four-info h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(30px, 4vw, 48px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.065em;
}

.vb-gallery-four-info p {
  margin: 0 !important;
  color: #4b5563 !important;
  -webkit-text-fill-color: #4b5563 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

@media (max-width: 980px) {
  .vb-gallery-four-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-four-card:nth-child(even) {
    transform: none;
  }

  .vb-gallery-four-card {
    min-height: 320px;
  }

  .vb-gallery-four-modal {
    grid-template-columns: 1fr;
    overflow: auto;
  }

  .vb-gallery-four-preview,
  .vb-gallery-four-preview img {
    min-height: 360px;
  }
}

@media (max-width: 560px) {
  .vb-gallery-four-demo {
    padding: 16px;
    border-radius: 26px;
  }

  .vb-gallery-four-top h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-four-grid {
    grid-template-columns: 1fr;
  }

  .vb-gallery-four-card {
    min-height: 290px;
    border-radius: 24px;
  }

  .vb-gallery-four-lightbox {
    padding: 12px;
  }

  .vb-gallery-four-modal {
    border-radius: 24px;
  }

  .vb-gallery-four-preview,
  .vb-gallery-four-preview img {
    min-height: 280px;
  }
}

This lightbox image gallery is useful for photography portfolios, ecommerce image previews, project case studies, real estate photos, restaurant galleries, event albums, and visual landing pages where users need a larger image view without leaving the page.

5. Lightbox Gallery with Next and Previous Buttons

A lightbox gallery with next and previous buttons is useful when visitors need to move through a full image set without closing the preview after every image. This pattern works well for photography portfolios, product galleries, project case studies, event albums, travel websites, real estate listings, and visual storytelling pages.

This example uses JavaScript index tracking. When a user opens an image, the script stores the current image index and updates the lightbox when the Next or Previous button is clicked. The image counter, title, category, and description all update dynamically.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-five]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-five-grid]");
  const lightbox = gallery.querySelector("[data-vb-gallery-five-lightbox]");
  const closeButtons = gallery.querySelectorAll("[data-vb-gallery-five-close]");
  const prevButton = gallery.querySelector("[data-vb-gallery-five-prev]");
  const nextButton = gallery.querySelector("[data-vb-gallery-five-next]");
  const imageEl = gallery.querySelector("[data-vb-gallery-five-image]");
  const titleEl = gallery.querySelector("[data-vb-gallery-five-title]");
  const categoryEl = gallery.querySelector("[data-vb-gallery-five-category]");
  const descriptionEl = gallery.querySelector("[data-vb-gallery-five-description]");
  const counterEl = gallery.querySelector("[data-vb-gallery-five-counter]");

  const items = [
    {
      title: "Blue Mountain View",
      category: "Landscape",
      description: "A wide landscape image for travel websites, hero galleries, and visual storytelling layouts.",
      thumb: "https://picsum.photos/seed/gallery-five-mountain/800/620",
      image: "https://picsum.photos/seed/gallery-five-mountain/1400/900"
    },
    {
      title: "Premium Studio Desk",
      category: "Workspace",
      description: "A clean workspace image for agency pages, personal brands, and productivity content.",
      thumb: "https://picsum.photos/seed/gallery-five-desk/800/620",
      image: "https://picsum.photos/seed/gallery-five-desk/1400/900"
    },
    {
      title: "Modern Product Scene",
      category: "Product",
      description: "A polished product image for ecommerce collections and launch pages.",
      thumb: "https://picsum.photos/seed/gallery-five-product/800/620",
      image: "https://picsum.photos/seed/gallery-five-product/1400/900"
    },
    {
      title: "Warm Interior Corner",
      category: "Interior",
      description: "A soft interior image for design galleries, renovation case studies, and home decor websites.",
      thumb: "https://picsum.photos/seed/gallery-five-interior/800/620",
      image: "https://picsum.photos/seed/gallery-five-interior/1400/900"
    },
    {
      title: "City Night Lines",
      category: "Architecture",
      description: "A structured city image for architecture portfolios and urban visual collections.",
      thumb: "https://picsum.photos/seed/gallery-five-city/800/620",
      image: "https://picsum.photos/seed/gallery-five-city/1400/900"
    },
    {
      title: "Editorial Travel Detail",
      category: "Travel",
      description: "A travel-focused image for destination guides, blogs, and editorial gallery sections.",
      thumb: "https://picsum.photos/seed/gallery-five-travel/800/620",
      image: "https://picsum.photos/seed/gallery-five-travel/1400/900"
    }
  ];

  let currentIndex = 0;

  function createCard(item, index) {
    return `
      <button type="button" class="vb-gallery-five-card" data-index="${index}">
        <img src="${item.thumb}" alt="${item.title}" loading="lazy">
        <div class="vb-gallery-five-card-content">
          <span>${item.category}</span>
          <strong>${item.title}</strong>
        </div>
      </button>
    `;
  }

  function renderGrid() {
    grid.innerHTML = items.map(createCard).join("");
  }

  function updateLightbox() {
    const item = items[currentIndex];

    imageEl.src = item.image;
    imageEl.alt = item.title;
    titleEl.textContent = item.title;
    categoryEl.textContent = item.category;
    descriptionEl.textContent = item.description;
    counterEl.textContent = currentIndex + 1 + " / " + items.length;
  }

  function openLightbox(index) {
    currentIndex = index;
    updateLightbox();
    lightbox.hidden = false;
    document.body.style.overflow = "hidden";
  }

  function closeLightbox() {
    lightbox.hidden = true;
    imageEl.src = "";
    document.body.style.overflow = "";
  }

  function showPrevious() {
    currentIndex = (currentIndex - 1 + items.length) % items.length;
    updateLightbox();
  }

  function showNext() {
    currentIndex = (currentIndex + 1) % items.length;
    updateLightbox();
  }

  grid.addEventListener("click", function (event) {
    const card = event.target.closest("[data-index]");
    if (!card) return;

    openLightbox(Number(card.getAttribute("data-index")));
  });

  closeButtons.forEach(function (button) {
    button.addEventListener("click", closeLightbox);
  });

  prevButton.addEventListener("click", showPrevious);
  nextButton.addEventListener("click", showNext);

  renderGrid();
})();

HTML

<div class="vb-gallery-five-demo">
  <div class="vb-gallery-five-shell" data-vb-gallery-five>
    <div class="vb-gallery-five-header">
      <div>
        <span>Example 05</span>
        <h3>Lightbox Gallery with Next and Previous Buttons</h3>
        <p>Browse a full image set inside a custom JavaScript lightbox without returning to the grid.</p>
      </div>
    </div>

    <div class="vb-gallery-five-grid" data-vb-gallery-five-grid></div>

    <div class="vb-gallery-five-lightbox" data-vb-gallery-five-lightbox hidden>
      <div class="vb-gallery-five-backdrop" data-vb-gallery-five-close></div>

      <div class="vb-gallery-five-modal" role="dialog" aria-modal="true" aria-label="Gallery image viewer">
        <button type="button" class="vb-gallery-five-close" data-vb-gallery-five-close>×</button>

        <div class="vb-gallery-five-image-stage">
          <button type="button" class="vb-gallery-five-nav vb-gallery-five-prev" data-vb-gallery-five-prev aria-label="Previous image">‹</button>
          <img src="" alt="" data-vb-gallery-five-image>
          <button type="button" class="vb-gallery-five-nav vb-gallery-five-next" data-vb-gallery-five-next aria-label="Next image">›</button>
        </div>

        <div class="vb-gallery-five-info">
          <div>
            <span data-vb-gallery-five-category></span>
            <h4 data-vb-gallery-five-title></h4>
            <p data-vb-gallery-five-description></p>
          </div>

          <strong data-vb-gallery-five-counter>1 / 1</strong>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-gallery-five-demo,
.vb-gallery-five-demo * {
  box-sizing: border-box;
}

.vb-gallery-five-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 46px 16px 46px 16px;
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.92), rgba(239, 246, 255, 0.94)),
    radial-gradient(circle at 12% 12%, rgba(37, 99, 235, 0.18), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(168, 85, 247, 0.18), transparent 34%) !important;
  border: 1px solid rgba(37, 99, 235, 0.20);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-gallery-five-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-five-header {
  margin-bottom: 24px;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 34px 10px 34px 10px;
  background:
    radial-gradient(circle at 12% 18%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, #172554, #312e81 52%, #581c87) !important;
  box-shadow: 0 22px 64px rgba(30, 41, 59, 0.22);
}

.vb-gallery-five-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.18);
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-five-header h3 {
  max-width: 850px;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-five-header p {
  max-width: 720px;
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-five-grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-five-card {
  position: relative;
  min-width: 0;
  min-height: 240px;
  overflow: hidden;
  padding: 0;
  border: 0;
  border-radius: 28px;
  background: #0f172a;
  cursor: pointer;
  text-align: left;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.14);
}

.vb-gallery-five-card:nth-child(1),
.vb-gallery-five-card:nth-child(6) {
  grid-column: span 3;
  min-height: 310px;
}

.vb-gallery-five-card:nth-child(2),
.vb-gallery-five-card:nth-child(3),
.vb-gallery-five-card:nth-child(4),
.vb-gallery-five-card:nth-child(5) {
  grid-column: span 3;
}

.vb-gallery-five-card img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.38s ease, filter 0.38s ease;
}

.vb-gallery-five-card:hover img {
  transform: scale(1.08);
  filter: saturate(1.12) contrast(1.05);
}

.vb-gallery-five-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(15, 23, 42, 0.02), rgba(15, 23, 42, 0.78));
}

.vb-gallery-five-card-content {
  position: absolute;
  z-index: 2;
  left: 18px;
  right: 18px;
  bottom: 18px;
  display: grid;
  gap: 7px;
}

.vb-gallery-five-card-content span {
  display: inline-flex;
  width: fit-content;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  backdrop-filter: blur(10px);
}

.vb-gallery-five-card-content strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.055em;
}

.vb-gallery-five-lightbox {
  position: fixed;
  inset: 0;
  z-index: 99999;
  display: grid;
  place-items: center;
  padding: clamp(14px, 4vw, 32px);
}

.vb-gallery-five-lightbox[hidden] {
  display: none;
}

.vb-gallery-five-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(2, 6, 23, 0.82);
  backdrop-filter: blur(14px);
}

.vb-gallery-five-modal {
  position: relative;
  z-index: 2;
  width: min(1060px, 100%);
  max-height: 92vh;
  overflow: hidden;
  border-radius: 36px;
  background: #ffffff;
  box-shadow: 0 34px 110px rgba(2, 6, 23, 0.48);
}

.vb-gallery-five-close {
  position: absolute;
  z-index: 5;
  top: 16px;
  right: 16px;
  width: 42px;
  height: 42px;
  border: 0;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.82);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 26px;
  line-height: 1;
  font-weight: 700;
  cursor: pointer;
}

.vb-gallery-five-image-stage {
  position: relative;
  min-height: 560px;
  background: #020617;
}

.vb-gallery-five-image-stage img {
  display: block;
  width: 100%;
  height: 560px;
  object-fit: cover;
}

.vb-gallery-five-nav {
  position: absolute;
  z-index: 4;
  top: 50%;
  transform: translateY(-50%);
  width: 54px;
  height: 54px;
  border: 1px solid rgba(255,255,255,0.24);
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.68);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 42px;
  line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(10px);
}

.vb-gallery-five-prev {
  left: 18px;
}

.vb-gallery-five-next {
  right: 18px;
}

.vb-gallery-five-info {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
  padding: clamp(20px, 3vw, 30px);
}

.vb-gallery-five-info span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #e0e7ff;
  color: #3730a3 !important;
  -webkit-text-fill-color: #3730a3 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-gallery-five-info h4 {
  margin: 0 0 8px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(28px, 4vw, 42px) !important;
  line-height: 1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-gallery-five-info p {
  max-width: 680px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-gallery-five-info strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 10px 13px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

@media (max-width: 760px) {
  .vb-gallery-five-grid {
    grid-template-columns: 1fr;
  }

  .vb-gallery-five-card,
  .vb-gallery-five-card:nth-child(1),
  .vb-gallery-five-card:nth-child(6),
  .vb-gallery-five-card:nth-child(2),
  .vb-gallery-five-card:nth-child(3),
  .vb-gallery-five-card:nth-child(4),
  .vb-gallery-five-card:nth-child(5) {
    grid-column: span 1;
    min-height: 270px;
  }

  .vb-gallery-five-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-five-modal {
    border-radius: 24px;
  }

  .vb-gallery-five-image-stage,
  .vb-gallery-five-image-stage img {
    height: 330px;
    min-height: 330px;
  }

  .vb-gallery-five-info {
    flex-direction: column;
  }

  .vb-gallery-five-nav {
    width: 46px;
    height: 46px;
    font-size: 34px;
  }
}

This lightbox gallery with next and previous buttons is useful for photography collections, product galleries, interior design projects, real estate image sets, travel galleries, event albums, and portfolio pages where users need to browse several images in one focused preview mode.

6. Keyboard Controlled Gallery Lightbox

A keyboard controlled gallery lightbox improves usability by letting users navigate images without using the mouse. This is especially useful for portfolio galleries, photography websites, media libraries, product previews, documentation pages, and accessible image viewers.

This example uses JavaScript keyboard event handling. Users can open the viewer, press the right arrow key for the next image, press the left arrow key for the previous image, and press Escape to close the lightbox. The visible keyboard hint and counter update with the current image.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-six]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-six-grid]");
  const viewer = gallery.querySelector("[data-vb-gallery-six-viewer]");
  const imageEl = gallery.querySelector("[data-vb-gallery-six-image]");
  const titleEl = gallery.querySelector("[data-vb-gallery-six-title]");
  const categoryEl = gallery.querySelector("[data-vb-gallery-six-category]");
  const descriptionEl = gallery.querySelector("[data-vb-gallery-six-description]");
  const counterEl = gallery.querySelector("[data-vb-gallery-six-counter]");
  const closeButtons = gallery.querySelectorAll("[data-vb-gallery-six-close]");
  const prevButton = gallery.querySelector("[data-vb-gallery-six-prev]");
  const nextButton = gallery.querySelector("[data-vb-gallery-six-next]");

  const items = [
    {
      title: "Gallery Keyboard One",
      category: "Portfolio",
      description: "A keyboard-friendly gallery item designed for portfolio image browsing and accessible preview controls.",
      thumb: "https://picsum.photos/seed/gallery-six-one/700/900",
      image: "https://picsum.photos/seed/gallery-six-one/1400/920"
    },
    {
      title: "Gallery Keyboard Two",
      category: "Editorial",
      description: "A visual editorial gallery item where users can navigate with arrow keys inside the viewer.",
      thumb: "https://picsum.photos/seed/gallery-six-two/700/700",
      image: "https://picsum.photos/seed/gallery-six-two/1400/920"
    },
    {
      title: "Gallery Keyboard Three",
      category: "Product",
      description: "A product gallery image preview with keyboard navigation for faster browsing.",
      thumb: "https://picsum.photos/seed/gallery-six-three/700/700",
      image: "https://picsum.photos/seed/gallery-six-three/1400/920"
    },
    {
      title: "Gallery Keyboard Four",
      category: "Travel",
      description: "A travel gallery item that supports keyboard-based movement between large image previews.",
      thumb: "https://picsum.photos/seed/gallery-six-four/700/900",
      image: "https://picsum.photos/seed/gallery-six-four/1400/920"
    },
    {
      title: "Gallery Keyboard Five",
      category: "Interior",
      description: "An interior design gallery item with clear title, category, and description in the lightbox.",
      thumb: "https://picsum.photos/seed/gallery-six-five/700/700",
      image: "https://picsum.photos/seed/gallery-six-five/1400/920"
    }
  ];

  let currentIndex = 0;

  function createTile(item, index) {
    return `
      <button type="button" class="vb-gallery-six-tile" data-index="${index}">
        <img src="${item.thumb}" alt="${item.title}" loading="lazy">
        <strong>${item.title}</strong>
      </button>
    `;
  }

  function renderGrid() {
    grid.innerHTML = items.map(createTile).join("");
  }

  function updateViewer() {
    const item = items[currentIndex];

    imageEl.src = item.image;
    imageEl.alt = item.title;
    titleEl.textContent = item.title;
    categoryEl.textContent = item.category;
    descriptionEl.textContent = item.description;
    counterEl.textContent = currentIndex + 1 + " / " + items.length;
  }

  function openViewer(index) {
    currentIndex = index;
    updateViewer();
    viewer.hidden = false;
    document.body.style.overflow = "hidden";
  }

  function closeViewer() {
    viewer.hidden = true;
    imageEl.src = "";
    document.body.style.overflow = "";
  }

  function showPrevious() {
    currentIndex = (currentIndex - 1 + items.length) % items.length;
    updateViewer();
  }

  function showNext() {
    currentIndex = (currentIndex + 1) % items.length;
    updateViewer();
  }

  grid.addEventListener("click", function (event) {
    const tile = event.target.closest("[data-index]");
    if (!tile) return;
    openViewer(Number(tile.getAttribute("data-index")));
  });

  closeButtons.forEach(function (button) {
    button.addEventListener("click", closeViewer);
  });

  prevButton.addEventListener("click", showPrevious);
  nextButton.addEventListener("click", showNext);

  document.addEventListener("keydown", function (event) {
    if (viewer.hidden) return;

    if (event.key === "Escape") {
      closeViewer();
    }

    if (event.key === "ArrowLeft") {
      showPrevious();
    }

    if (event.key === "ArrowRight") {
      showNext();
    }
  });

  renderGrid();
})();

HTML

<div class="vb-gallery-six-demo">
  <div class="vb-gallery-six-shell" data-vb-gallery-six>
    <div class="vb-gallery-six-sidebar">
      <span>Example 06</span>
      <h3>Keyboard Controlled Gallery Lightbox</h3>
      <p>Open the gallery and use arrow keys to move between images. Press Escape to close the viewer.</p>

      <div class="vb-gallery-six-keys">
        <kbd>←</kbd>
        <kbd>→</kbd>
        <kbd>Esc</kbd>
      </div>
    </div>

    <div class="vb-gallery-six-grid" data-vb-gallery-six-grid></div>

    <div class="vb-gallery-six-viewer" data-vb-gallery-six-viewer hidden>
      <div class="vb-gallery-six-overlay" data-vb-gallery-six-close></div>

      <div class="vb-gallery-six-panel" role="dialog" aria-modal="true" aria-label="Keyboard controlled gallery viewer">
        <div class="vb-gallery-six-toolbar">
          <span data-vb-gallery-six-counter>1 / 1</span>
          <strong>Use ← → keys to browse</strong>
          <button type="button" data-vb-gallery-six-close>Close</button>
        </div>

        <div class="vb-gallery-six-photo-wrap">
          <button type="button" class="vb-gallery-six-arrow vb-gallery-six-left" data-vb-gallery-six-prev aria-label="Previous image">←</button>
          <img src="" alt="" data-vb-gallery-six-image>
          <button type="button" class="vb-gallery-six-arrow vb-gallery-six-right" data-vb-gallery-six-next aria-label="Next image">→</button>
        </div>

        <div class="vb-gallery-six-caption">
          <span data-vb-gallery-six-category></span>
          <h4 data-vb-gallery-six-title></h4>
          <p data-vb-gallery-six-description></p>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-gallery-six-demo,
.vb-gallery-six-demo * {
  box-sizing: border-box;
}

.vb-gallery-six-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 26px;
  background:
    linear-gradient(90deg, rgba(15, 23, 42, 0.05) 1px, transparent 1px),
    linear-gradient(0deg, rgba(15, 23, 42, 0.05) 1px, transparent 1px),
    linear-gradient(135deg, #fafaf9 0%, #f5f5f4 52%, #ffffff 100%) !important;
  background-size: 30px 30px, 30px 30px, auto !important;
  border: 1px solid rgba(120, 113, 108, 0.18);
  box-shadow: 0 28px 80px rgba(68, 64, 60, 0.10);
}

.vb-gallery-six-shell {
  display: grid;
  grid-template-columns: minmax(260px, 340px) minmax(0, 1fr);
  gap: 18px;
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-six-sidebar {
  display: grid;
  align-content: center;
  min-width: 0;
  padding: clamp(22px, 4vw, 32px);
  border-radius: 24px;
  background:
    radial-gradient(circle at 18% 14%, rgba(255,255,255,0.14), transparent 34%),
    linear-gradient(135deg, #1c1917, #44403c) !important;
  box-shadow: 0 22px 64px rgba(28, 25, 23, 0.20);
}

.vb-gallery-six-sidebar span {
  display: inline-flex;
  width: fit-content;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-six-sidebar h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 4.8vw, 54px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-six-sidebar p {
  margin: 0 0 22px !important;
  color: #e7e5e4 !important;
  -webkit-text-fill-color: #e7e5e4 !important;
  font-size: 15px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-six-keys {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.vb-gallery-six-keys kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 48px;
  height: 42px;
  padding: 0 13px;
  border-radius: 12px;
  background: #ffffff;
  color: #1c1917;
  font-size: 14px;
  font-weight: 950;
  box-shadow: inset 0 -3px 0 rgba(15, 23, 42, 0.14);
}

.vb-gallery-six-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.vb-gallery-six-tile {
  position: relative;
  min-height: 235px;
  overflow: hidden;
  border: 0;
  border-radius: 24px;
  background: #292524;
  cursor: pointer;
  text-align: left;
  box-shadow: 0 16px 44px rgba(41, 37, 36, 0.14);
}

.vb-gallery-six-tile:nth-child(1),
.vb-gallery-six-tile:nth-child(4) {
  grid-row: span 2;
  min-height: 482px;
}

.vb-gallery-six-tile img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  transition: transform 0.38s ease, filter 0.38s ease;
}

.vb-gallery-six-tile:hover img {
  transform: scale(1.08);
  filter: contrast(1.05) saturate(1.08);
}

.vb-gallery-six-tile::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(28, 25, 23, 0.03), rgba(28, 25, 23, 0.78));
}

.vb-gallery-six-tile strong {
  position: absolute;
  z-index: 2;
  left: 16px;
  right: 16px;
  bottom: 16px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 20px;
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.045em;
}

.vb-gallery-six-viewer {
  position: fixed;
  inset: 0;
  z-index: 99999;
  display: grid;
  place-items: center;
  padding: clamp(12px, 3vw, 28px);
}

.vb-gallery-six-viewer[hidden] {
  display: none;
}

.vb-gallery-six-overlay {
  position: absolute;
  inset: 0;
  background: rgba(12, 10, 9, 0.86);
  backdrop-filter: blur(14px);
}

.vb-gallery-six-panel {
  position: relative;
  z-index: 2;
  width: min(1080px, 100%);
  max-height: 92vh;
  overflow: hidden;
  border-radius: 28px;
  background: #fafaf9;
  box-shadow: 0 34px 110px rgba(0, 0, 0, 0.48);
}

.vb-gallery-six-toolbar {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 14px;
  background: #1c1917;
}

.vb-gallery-six-toolbar span,
.vb-gallery-six-toolbar strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-gallery-six-toolbar strong {
  text-align: center;
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
}

.vb-gallery-six-toolbar button {
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: #1c1917;
  padding: 9px 12px;
  font-size: 12px;
  font-weight: 950;
  cursor: pointer;
}

.vb-gallery-six-photo-wrap {
  position: relative;
  height: 560px;
  background: #0c0a09;
}

.vb-gallery-six-photo-wrap img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.vb-gallery-six-arrow {
  position: absolute;
  z-index: 3;
  top: 50%;
  transform: translateY(-50%);
  width: 54px;
  height: 54px;
  border: 1px solid rgba(255,255,255,0.24);
  border-radius: 999px;
  background: rgba(28, 25, 23, 0.72);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  font-weight: 950;
  cursor: pointer;
  backdrop-filter: blur(10px);
}

.vb-gallery-six-left {
  left: 18px;
}

.vb-gallery-six-right {
  right: 18px;
}

.vb-gallery-six-caption {
  padding: clamp(20px, 3vw, 28px);
}

.vb-gallery-six-caption span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-gallery-six-caption h4 {
  margin: 0 0 8px !important;
  color: #1c1917 !important;
  -webkit-text-fill-color: #1c1917 !important;
  font-size: clamp(28px, 4vw, 42px) !important;
  line-height: 1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-gallery-six-caption p {
  max-width: 760px;
  margin: 0 !important;
  color: #57534e !important;
  -webkit-text-fill-color: #57534e !important;
  font-size: 15px;
  line-height: 1.7;
  font-weight: 650;
}

@media (max-width: 980px) {
  .vb-gallery-six-shell {
    grid-template-columns: 1fr;
  }

  .vb-gallery-six-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-six-tile:nth-child(1),
  .vb-gallery-six-tile:nth-child(4) {
    grid-row: span 1;
    min-height: 260px;
  }
}

@media (max-width: 620px) {
  .vb-gallery-six-demo {
    padding: 16px;
  }

  .vb-gallery-six-sidebar h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-six-grid {
    grid-template-columns: 1fr;
  }

  .vb-gallery-six-tile {
    min-height: 260px;
  }

  .vb-gallery-six-toolbar {
    grid-template-columns: 1fr;
    text-align: center;
  }

  .vb-gallery-six-photo-wrap {
    height: 320px;
  }

  .vb-gallery-six-arrow {
    width: 44px;
    height: 44px;
  }
}

This keyboard controlled gallery lightbox is useful for accessible image galleries, photography portfolios, media collections, product previews, editorial pages, and visual interfaces where users should be able to browse images with keyboard controls.

7. Masonry Image Gallery

A masonry image gallery is useful when images have different heights and should flow into a Pinterest-style layout instead of a strict equal-height grid. This pattern works well for photography galleries, moodboards, portfolio collections, travel blogs, interior inspiration pages, and editorial image layouts.

This example uses JavaScript masonry distribution logic. Instead of relying only on CSS columns, JavaScript splits the gallery items into balanced columns and always adds the next card to the shortest column. This creates a more controlled masonry layout and keeps the gallery visually balanced.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-seven]");
  if (!gallery) return;

  const columnsWrap = gallery.querySelector("[data-vb-gallery-seven-columns]");
  const countEl = gallery.querySelector("[data-vb-gallery-seven-count]");
  const rebuildButton = gallery.querySelector("[data-vb-gallery-seven-rebuild]");

  const items = [
    {
      title: "Tall Editorial Shot",
      category: "Editorial",
      description: "A tall gallery card for editorial image layouts and visual story pages.",
      image: "https://picsum.photos/seed/gallery-seven-editorial/720/1040",
      heightScore: 1040
    },
    {
      title: "Compact Product Frame",
      category: "Product",
      description: "A shorter card for ecommerce collections and product-focused galleries.",
      image: "https://picsum.photos/seed/gallery-seven-product/720/620",
      heightScore: 620
    },
    {
      title: "Interior Moodboard",
      category: "Interior",
      description: "A medium-height interior card for inspiration boards and design collections.",
      image: "https://picsum.photos/seed/gallery-seven-interior/720/860",
      heightScore: 860
    },
    {
      title: "Travel Journal Image",
      category: "Travel",
      description: "A tall travel visual for destination galleries and blog collections.",
      image: "https://picsum.photos/seed/gallery-seven-travel/720/980",
      heightScore: 980
    },
    {
      title: "Architecture Detail",
      category: "Architecture",
      description: "A wide-feeling architecture card with a balanced visual ratio.",
      image: "https://picsum.photos/seed/gallery-seven-architecture/720/700",
      heightScore: 700
    },
    {
      title: "Studio Campaign",
      category: "Studio",
      description: "A polished studio image for agency work and brand presentation galleries.",
      image: "https://picsum.photos/seed/gallery-seven-studio/720/900",
      heightScore: 900
    },
    {
      title: "Nature Texture",
      category: "Nature",
      description: "A compact nature card for moodboards and visual inspiration pages.",
      image: "https://picsum.photos/seed/gallery-seven-nature/720/640",
      heightScore: 640
    },
    {
      title: "Creative Workspace",
      category: "Workspace",
      description: "A taller workspace gallery card for portfolio and blog page layouts.",
      image: "https://picsum.photos/seed/gallery-seven-workspace/720/1060",
      heightScore: 1060
    },
    {
      title: "Urban Night Scene",
      category: "Urban",
      description: "A medium gallery card for street photography and city collections.",
      image: "https://picsum.photos/seed/gallery-seven-urban/720/820",
      heightScore: 820
    }
  ];

  function getColumnCount() {
    if (window.matchMedia("(max-width: 520px)").matches) return 1;
    if (window.matchMedia("(max-width: 760px)").matches) return 2;
    if (window.matchMedia("(max-width: 1050px)").matches) return 3;
    return 4;
  }

  function createCard(item, index) {
    return `
      <article class="vb-gallery-seven-card" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-seven-image">
          <img src="${item.image}" alt="${item.title}" loading="lazy">
          <span>${item.category}</span>
        </div>

        <div class="vb-gallery-seven-body">
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function distributeItems(itemList, columnCount) {
    const columns = Array.from({ length: columnCount }, function () {
      return {
        height: 0,
        items: []
      };
    });

    itemList.forEach(function (item) {
      const shortestColumn = columns.reduce(function (shortest, column) {
        return column.height < shortest.height ? column : shortest;
      }, columns[0]);

      shortestColumn.items.push(item);
      shortestColumn.height += item.heightScore;
    });

    return columns;
  }

  function renderMasonry() {
    const columnCount = getColumnCount();
    const columns = distributeItems(items, columnCount);

    columnsWrap.innerHTML = columns.map(function (column) {
      return `
        <div class="vb-gallery-seven-column">
          ${column.items.map(createCard).join("")}
        </div>
      `;
    }).join("");

    countEl.textContent = items.length + " masonry images";
  }

  rebuildButton.addEventListener("click", function () {
    renderMasonry();
  });

  window.addEventListener("resize", function () {
    renderMasonry();
  });

  renderMasonry();
})();

HTML

<div class="vb-gallery-seven-demo">
  <div class="vb-gallery-seven-shell" data-vb-gallery-seven>
    <div class="vb-gallery-seven-header">
      <span>Example 07</span>
      <h3>Masonry Image Gallery</h3>
      <p>A JavaScript-powered masonry gallery that balances image cards into the shortest available column.</p>
    </div>

    <div class="vb-gallery-seven-stats">
      <strong data-vb-gallery-seven-count>0 images</strong>
      <button type="button" data-vb-gallery-seven-rebuild>Rebuild Layout</button>
    </div>

    <div class="vb-gallery-seven-columns" data-vb-gallery-seven-columns></div>
  </div>
</div>

CSS

.vb-gallery-seven-demo,
.vb-gallery-seven-demo * {
  box-sizing: border-box;
}

.vb-gallery-seven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(217, 70, 239, 0.16), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(14, 165, 233, 0.16), transparent 36%),
    linear-gradient(135deg, #fdf4ff 0%, #f0f9ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(192, 132, 252, 0.24);
  box-shadow: 0 28px 80px rgba(88, 28, 135, 0.10);
}

.vb-gallery-seven-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-seven-header {
  max-width: 820px;
  margin-bottom: 22px;
}

.vb-gallery-seven-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #fae8ff;
  color: #a21caf !important;
  -webkit-text-fill-color: #a21caf !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-seven-header h3 {
  margin: 0 0 14px !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-seven-header p {
  margin: 0 !important;
  color: #4b5563 !important;
  -webkit-text-fill-color: #4b5563 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-seven-stats {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 18px;
  padding: 12px;
  border-radius: 22px;
  background: rgba(255,255,255,0.78);
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.08);
}

.vb-gallery-seven-stats strong {
  display: inline-flex;
  padding: 10px 13px;
  border-radius: 999px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-gallery-seven-stats button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #d946ef, #0ea5e9);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 12px 16px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 32px rgba(217, 70, 239, 0.24);
}

.vb-gallery-seven-columns {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
  align-items: start;
}

.vb-gallery-seven-column {
  display: grid;
  gap: 16px;
  min-width: 0;
}

.vb-gallery-seven-card {
  overflow: hidden;
  border-radius: 26px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGallerySevenRise 0.42s ease both;
}

@keyframes vbGallerySevenRise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.vb-gallery-seven-image {
  position: relative;
  overflow: hidden;
  background: #e5e7eb;
}

.vb-gallery-seven-image img {
  display: block;
  width: 100%;
  height: auto;
  min-height: 180px;
  object-fit: cover;
  transition: transform 0.38s ease, filter 0.38s ease;
}

.vb-gallery-seven-card:hover img {
  transform: scale(1.07);
  filter: saturate(1.1) contrast(1.04);
}

.vb-gallery-seven-image span {
  position: absolute;
  left: 13px;
  top: 13px;
  display: inline-flex;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(17, 24, 39, 0.72);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  backdrop-filter: blur(10px);
}

.vb-gallery-seven-body {
  display: grid;
  gap: 8px;
  padding: 16px;
}

.vb-gallery-seven-body h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 19px !important;
  line-height: 1.16 !important;
  font-weight: 950 !important;
  letter-spacing: -0.04em;
}

.vb-gallery-seven-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.6;
  font-weight: 650;
}

@media (max-width: 1050px) {
  .vb-gallery-seven-columns {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 760px) {
  .vb-gallery-seven-columns {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-seven-stats {
    align-items: stretch;
    flex-direction: column;
  }

  .vb-gallery-seven-stats button,
  .vb-gallery-seven-stats strong {
    justify-content: center;
    width: 100%;
  }
}

@media (max-width: 520px) {
  .vb-gallery-seven-demo {
    padding: 16px;
    border-radius: 26px;
  }

  .vb-gallery-seven-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-seven-columns {
    grid-template-columns: 1fr;
  }
}

This masonry image gallery is useful for photography collections, Pinterest-style inspiration boards, portfolio archives, travel image layouts, interior design galleries, moodboards, and editorial pages where images have different heights.

8. Lazy Loading Image Gallery

A lazy loading image gallery improves performance by loading images only when they are close to the visible viewport. This is useful for large gallery pages, image-heavy blog posts, ecommerce collections, photography portfolios, travel guides, real estate listings, and long landing pages.

This example uses the JavaScript IntersectionObserver API. Each image starts with a lightweight placeholder and stores the real image URL in a data attribute. When the image card becomes visible, JavaScript replaces the placeholder with the real image and marks the card as loaded.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-eight]");
  if (!gallery) return;

  const images = gallery.querySelectorAll("img[data-src]");
  const statusEl = gallery.querySelector("[data-vb-gallery-eight-status]");
  let loadedCount = 0;

  function updateStatus() {
    statusEl.textContent = loadedCount + " / " + images.length + " images loaded";
  }

  function loadImage(image) {
    const realSource = image.getAttribute("data-src");
    if (!realSource) return;

    image.src = realSource;
    image.removeAttribute("data-src");

    image.addEventListener("load", function () {
      loadedCount += 1;
      image.closest(".vb-gallery-eight-card").classList.add("is-loaded");
      updateStatus();
    }, { once: true });
  }

  if ("IntersectionObserver" in window) {
    const observer = new IntersectionObserver(function (entries, currentObserver) {
      entries.forEach(function (entry) {
        if (!entry.isIntersecting) return;

        loadImage(entry.target);
        currentObserver.unobserve(entry.target);
      });
    }, {
      root: null,
      rootMargin: "160px 0px",
      threshold: 0.1
    });

    images.forEach(function (image) {
      observer.observe(image);
    });
  } else {
    images.forEach(loadImage);
  }

  updateStatus();
})();

HTML

<div class="vb-gallery-eight-demo">
  <div class="vb-gallery-eight-shell" data-vb-gallery-eight>
    <div class="vb-gallery-eight-top">
      <div>
        <span>Example 08</span>
        <h3>Lazy Loading Image Gallery</h3>
        <p>Load gallery images only when they approach the viewport using JavaScript IntersectionObserver.</p>
      </div>

      <strong data-vb-gallery-eight-status>Waiting for images</strong>
    </div>

    <div class="vb-gallery-eight-grid">
      <article class="vb-gallery-eight-card">
        <div class="vb-gallery-eight-photo">
          <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='620'%3E%3Crect width='900' height='620' fill='%23e0f2fe'/%3E%3C/svg%3E" data-src="https://picsum.photos/seed/gallery-eight-one/900/620" alt="Lazy loaded gallery image one">
        </div>
        <div>
          <span>01</span>
          <h4>Deferred Portfolio Image</h4>
          <p>This image loads when the card becomes visible inside the viewport.</p>
        </div>
      </article>

      <article class="vb-gallery-eight-card">
        <div class="vb-gallery-eight-photo">
          <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='760'%3E%3Crect width='900' height='760' fill='%23ddd6fe'/%3E%3C/svg%3E" data-src="https://picsum.photos/seed/gallery-eight-two/900/760" alt="Lazy loaded gallery image two">
        </div>
        <div>
          <span>02</span>
          <h4>Image Near Viewport</h4>
          <p>IntersectionObserver watches each image and loads it at the right time.</p>
        </div>
      </article>

      <article class="vb-gallery-eight-card">
        <div class="vb-gallery-eight-photo">
          <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='680'%3E%3Crect width='900' height='680' fill='%23dcfce7'/%3E%3C/svg%3E" data-src="https://picsum.photos/seed/gallery-eight-three/900/680" alt="Lazy loaded gallery image three">
        </div>
        <div>
          <span>03</span>
          <h4>Performance Gallery Card</h4>
          <p>Useful for long galleries where loading every image immediately is not ideal.</p>
        </div>
      </article>

      <article class="vb-gallery-eight-card">
        <div class="vb-gallery-eight-photo">
          <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='720'%3E%3Crect width='900' height='720' fill='%23ffedd5'/%3E%3C/svg%3E" data-src="https://picsum.photos/seed/gallery-eight-four/900/720" alt="Lazy loaded gallery image four">
        </div>
        <div>
          <span>04</span>
          <h4>Scroll Activated Image</h4>
          <p>The gallery marks each card as loaded after the full image has loaded.</p>
        </div>
      </article>

      <article class="vb-gallery-eight-card">
        <div class="vb-gallery-eight-photo">
          <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='640'%3E%3Crect width='900' height='640' fill='%23fce7f3'/%3E%3C/svg%3E" data-src="https://picsum.photos/seed/gallery-eight-five/900/640" alt="Lazy loaded gallery image five">
        </div>
        <div>
          <span>05</span>
          <h4>Optimized Visual Section</h4>
          <p>Lazy loading can improve perceived speed on image-heavy website sections.</p>
        </div>
      </article>

      <article class="vb-gallery-eight-card">
        <div class="vb-gallery-eight-photo">
          <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='800'%3E%3Crect width='900' height='800' fill='%23ccfbf1'/%3E%3C/svg%3E" data-src="https://picsum.photos/seed/gallery-eight-six/900/800" alt="Lazy loaded gallery image six">
        </div>
        <div>
          <span>06</span>
          <h4>Large Gallery Ready</h4>
          <p>This pattern can be reused for portfolios, catalogs, image archives, and galleries.</p>
        </div>
      </article>
    </div>
  </div>
</div>

CSS

.vb-gallery-eight-demo,
.vb-gallery-eight-demo * {
  box-sizing: border-box;
}

.vb-gallery-eight-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 18px;
  background:
    radial-gradient(circle at 12% 12%, rgba(59, 130, 246, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ffffff 54%, #f8fafc 100%) !important;
  border: 1px solid rgba(59, 130, 246, 0.20);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-gallery-eight-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-eight-top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 24px;
}

.vb-gallery-eight-top span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 10px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-eight-top h3 {
  max-width: 760px;
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-eight-top p {
  max-width: 740px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-eight-top > strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-eight-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-gallery-eight-card {
  overflow: hidden;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.09);
}

.vb-gallery-eight-photo {
  position: relative;
  aspect-ratio: 1.25 / 1;
  overflow: hidden;
  background:
    linear-gradient(90deg, #e2e8f0 0%, #f8fafc 48%, #e2e8f0 100%);
  background-size: 220% 100%;
  animation: vbGalleryEightShimmer 1.4s linear infinite;
}

@keyframes vbGalleryEightShimmer {
  0% {
    background-position: 120% 0;
  }

  100% {
    background-position: -120% 0;
  }
}

.vb-gallery-eight-card.is-loaded .vb-gallery-eight-photo {
  animation: none;
  background: #e2e8f0;
}

.vb-gallery-eight-photo::after {
  content: "Loading image";
  position: absolute;
  left: 16px;
  bottom: 16px;
  z-index: 2;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.72);
  color: #ffffff;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  backdrop-filter: blur(10px);
}

.vb-gallery-eight-card.is-loaded .vb-gallery-eight-photo::after {
  content: "Loaded";
  background: rgba(22, 163, 74, 0.82);
}

.vb-gallery-eight-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.22;
  filter: blur(12px);
  transform: scale(1.04);
  transition: opacity 0.42s ease, filter 0.42s ease, transform 0.42s ease;
}

.vb-gallery-eight-card.is-loaded img {
  opacity: 1;
  filter: blur(0);
  transform: scale(1);
}

.vb-gallery-eight-card > div:last-child {
  display: grid;
  gap: 8px;
  padding: 17px;
}

.vb-gallery-eight-card span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #eff6ff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-eight-card h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 20px !important;
  line-height: 1.16 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-eight-card p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.6;
  font-weight: 650;
}

@media (max-width: 980px) {
  .vb-gallery-eight-top {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-eight-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 620px) {
  .vb-gallery-eight-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-eight-top h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-eight-grid {
    grid-template-columns: 1fr;
  }

  .vb-gallery-eight-top > strong {
    justify-content: center;
    width: 100%;
  }
}

This lazy loading image gallery is useful for large image collections, portfolio pages, ecommerce category pages, photography archives, travel galleries, real estate listings, and long blog posts where performance matters.

9. Infinite Scroll Image Gallery

An infinite scroll image gallery loads more images automatically when the visitor reaches the bottom of the gallery. This is useful for portfolio archives, inspiration boards, photography collections, ecommerce galleries, travel pages, media libraries, and long visual browsing experiences.

This example uses JavaScript IntersectionObserver logic. A small loader element at the bottom of the gallery is observed, and when it enters the viewport, the next batch of images is rendered automatically. The layout is clean, responsive, and built so every image fills the full card area without unwanted top spacing.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-nine]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-nine-grid]");
  const loader = gallery.querySelector("[data-vb-gallery-nine-loader]");
  const loaderText = gallery.querySelector("[data-vb-gallery-nine-loader-text]");
  const countEl = gallery.querySelector("[data-vb-gallery-nine-count]");

  const items = [
    { title: "Infinite Gallery One", category: "Portfolio", description: "A loaded image card for endless visual browsing.", image: "https://picsum.photos/seed/gallery-nine-1/900/700" },
    { title: "Infinite Gallery Two", category: "Travel", description: "A travel-inspired image card loaded in the first batch.", image: "https://picsum.photos/seed/gallery-nine-2/900/700" },
    { title: "Infinite Gallery Three", category: "Interior", description: "A clean interior image card for scroll-based galleries.", image: "https://picsum.photos/seed/gallery-nine-3/900/700" },
    { title: "Infinite Gallery Four", category: "Product", description: "A product-style visual card loaded after scrolling.", image: "https://picsum.photos/seed/gallery-nine-4/900/700" },
    { title: "Infinite Gallery Five", category: "Architecture", description: "A structured architecture card added by JavaScript.", image: "https://picsum.photos/seed/gallery-nine-5/900/700" },
    { title: "Infinite Gallery Six", category: "Nature", description: "A nature card added as part of the next image batch.", image: "https://picsum.photos/seed/gallery-nine-6/900/700" },
    { title: "Infinite Gallery Seven", category: "Studio", description: "A creative studio card for long image collections.", image: "https://picsum.photos/seed/gallery-nine-7/900/700" },
    { title: "Infinite Gallery Eight", category: "Workspace", description: "A workspace card loaded when the observer is triggered.", image: "https://picsum.photos/seed/gallery-nine-8/900/700" },
    { title: "Infinite Gallery Nine", category: "Editorial", description: "An editorial card for media-heavy gallery pages.", image: "https://picsum.photos/seed/gallery-nine-9/900/700" },
    { title: "Infinite Gallery Ten", category: "Lifestyle", description: "A lifestyle card appended without refreshing the page.", image: "https://picsum.photos/seed/gallery-nine-10/900/700" },
    { title: "Infinite Gallery Eleven", category: "Urban", description: "An urban card for automatic scroll loading.", image: "https://picsum.photos/seed/gallery-nine-11/900/700" },
    { title: "Infinite Gallery Twelve", category: "Gallery", description: "The final card in this infinite scroll demo.", image: "https://picsum.photos/seed/gallery-nine-12/900/700" }
  ];

  const batchSize = 3;
  let loadedItems = 0;
  let isLoading = false;

  function createCard(item, index) {
    return `
      <article class="vb-gallery-nine-card" style="animation-delay: ${index * 45}ms">
        <img src="${item.image}" alt="${item.title}" loading="lazy">

        <div class="vb-gallery-nine-content">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function updateCount() {
    countEl.textContent = loadedItems + " / " + items.length + " images loaded";
  }

  function loadNextBatch() {
    if (isLoading) return;
    if (loadedItems >= items.length) return;

    isLoading = true;
    loaderText.textContent = "Loading more images...";

    window.setTimeout(function () {
      const nextItems = items.slice(loadedItems, loadedItems + batchSize);
      const markup = nextItems.map(function (item, index) {
        return createCard(item, index);
      }).join("");

      grid.insertAdjacentHTML("beforeend", markup);
      loadedItems += nextItems.length;
      updateCount();

      if (loadedItems >= items.length) {
        loaderText.textContent = "All images loaded";
        loader.querySelector("span").style.display = "none";
      } else {
        loaderText.textContent = "Scroll down to load more images";
      }

      isLoading = false;
    }, 450);
  }

  if ("IntersectionObserver" in window) {
    const observer = new IntersectionObserver(function (entries) {
      entries.forEach(function (entry) {
        if (entry.isIntersecting) {
          loadNextBatch();
        }
      });
    }, {
      root: null,
      rootMargin: "180px 0px",
      threshold: 0.1
    });

    observer.observe(loader);
  }

  loadNextBatch();
})();

HTML

<div class="vb-gallery-nine-demo">
  <div class="vb-gallery-nine-shell" data-vb-gallery-nine>
    <div class="vb-gallery-nine-head">
      <div>
        <span>Example 09</span>
        <h3>Infinite Scroll Image Gallery</h3>
        <p>Automatically load more image cards when the visitor reaches the bottom of the gallery.</p>
      </div>

      <strong data-vb-gallery-nine-count>0 images loaded</strong>
    </div>

    <div class="vb-gallery-nine-grid" data-vb-gallery-nine-grid></div>

    <div class="vb-gallery-nine-loader" data-vb-gallery-nine-loader>
      <span></span>
      <p data-vb-gallery-nine-loader-text>Scroll down to load more images</p>
    </div>
  </div>
</div>

CSS

.vb-gallery-nine-card img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  min-width: 100%;
  min-height: 100%;
  object-fit: cover;
  object-position: center center;
  border: 0;
  margin: 0;
  padding: 0;
}

This infinite scroll image gallery is useful for portfolio archives, image-heavy blogs, ecommerce galleries, photography websites, inspiration boards, travel collections, and visual pages where new images should load automatically as users scroll.

10. Load More Image Gallery

A load more image gallery gives users control over how many images they want to see. Instead of loading the full gallery immediately, the page shows an initial set of images and reveals more when the user clicks a button.

This example uses JavaScript visible count logic. The gallery starts with four images, then adds more in groups of four. The progress bar, visible count, and button state update automatically. This is useful when you want better performance and a more controlled browsing experience than infinite scroll.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-ten]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-ten-grid]");
  const button = gallery.querySelector("[data-vb-gallery-ten-button]");
  const countEl = gallery.querySelector("[data-vb-gallery-ten-count]");
  const barEl = gallery.querySelector("[data-vb-gallery-ten-bar]");

  const items = [
    { title: "Load More One", category: "Portfolio", image: "https://picsum.photos/seed/gallery-ten-1/900/700" },
    { title: "Load More Two", category: "Interior", image: "https://picsum.photos/seed/gallery-ten-2/900/700" },
    { title: "Load More Three", category: "Product", image: "https://picsum.photos/seed/gallery-ten-3/900/700" },
    { title: "Load More Four", category: "Travel", image: "https://picsum.photos/seed/gallery-ten-4/900/700" },
    { title: "Load More Five", category: "Architecture", image: "https://picsum.photos/seed/gallery-ten-5/900/700" },
    { title: "Load More Six", category: "Studio", image: "https://picsum.photos/seed/gallery-ten-6/900/700" },
    { title: "Load More Seven", category: "Nature", image: "https://picsum.photos/seed/gallery-ten-7/900/700" },
    { title: "Load More Eight", category: "Workspace", image: "https://picsum.photos/seed/gallery-ten-8/900/700" },
    { title: "Load More Nine", category: "Editorial", image: "https://picsum.photos/seed/gallery-ten-9/900/700" },
    { title: "Load More Ten", category: "Lifestyle", image: "https://picsum.photos/seed/gallery-ten-10/900/700" },
    { title: "Load More Eleven", category: "Urban", image: "https://picsum.photos/seed/gallery-ten-11/900/700" },
    { title: "Load More Twelve", category: "Gallery", image: "https://picsum.photos/seed/gallery-ten-12/900/700" }
  ];

  const step = 4;
  let visibleCount = 4;

  function createCard(item, index) {
    return `
      <article class="vb-gallery-ten-card" style="animation-delay: ${index * 45}ms">
        <img src="${item.image}" alt="${item.title}" loading="lazy">

        <div class="vb-gallery-ten-content">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
        </div>
      </article>
    `;
  }

  function updateProgress() {
    const percent = Math.round((visibleCount / items.length) * 100);
    countEl.textContent = "Showing " + visibleCount + " of " + items.length + " images";
    barEl.style.width = percent + "%";
    button.hidden = visibleCount >= items.length;
  }

  function renderGallery() {
    const visibleItems = items.slice(0, visibleCount);
    grid.innerHTML = visibleItems.map(createCard).join("");
    updateProgress();
  }

  button.addEventListener("click", function () {
    visibleCount = Math.min(visibleCount + step, items.length);
    renderGallery();
  });

  renderGallery();
})();

HTML

<div class="vb-gallery-ten-demo">
  <div class="vb-gallery-ten-shell" data-vb-gallery-ten>
    <div class="vb-gallery-ten-panel">
      <span>Example 10</span>
      <h3>Load More Image Gallery</h3>
      <p>Reveal more image cards on button click with JavaScript count state and progress feedback.</p>

      <div class="vb-gallery-ten-progress">
        <strong data-vb-gallery-ten-count>Showing 0 of 0</strong>
        <div>
          <span data-vb-gallery-ten-bar></span>
        </div>
      </div>
    </div>

    <div class="vb-gallery-ten-grid" data-vb-gallery-ten-grid></div>

    <div class="vb-gallery-ten-actions">
      <button type="button" data-vb-gallery-ten-button>Load More Images</button>
    </div>
  </div>
</div>

CSS

.vb-gallery-ten-card img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  min-width: 100%;
  min-height: 100%;
  object-fit: cover;
  object-position: center center;
  border: 0;
  margin: 0;
  padding: 0;
}

This load more image gallery is useful for portfolio pages, ecommerce image grids, travel collections, media libraries, blog galleries, real estate listings, and any visual section where users should control how many images appear at once.

11. Sortable Image Gallery

A sortable image gallery lets users change the order of images by title, date, popularity, or another custom value. This is useful for portfolio archives, product collections, media libraries, design galleries, real estate listings, case studies, and any image-heavy page where the same gallery data should be displayed in different orders.

This example uses JavaScript array sorting and re-rendering. The gallery items are stored in a reusable data array, and each sort button changes how the cards appear. The visual thumbnails are created with CSS only, so the demo does not depend on any external image URLs.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-eleven]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-eleven-grid]");
  const controls = gallery.querySelector("[data-vb-gallery-eleven-controls]");
  const status = gallery.querySelector("[data-vb-gallery-eleven-status]");

  const items = [
    {
      title: "Aurora Field",
      category: "Landscape",
      year: 2026,
      views: 1280,
      art: "vb-gallery-eleven-art-aurora",
      description: "A CSS-only aurora style thumbnail for a modern gallery card."
    },
    {
      title: "Desert Sun",
      category: "Travel",
      year: 2023,
      views: 940,
      art: "vb-gallery-eleven-art-desert",
      description: "A warm CSS desert illustration for visual browsing sections."
    },
    {
      title: "Ocean Motion",
      category: "Nature",
      year: 2025,
      views: 1760,
      art: "vb-gallery-eleven-art-ocean",
      description: "A CSS ocean scene for image gallery examples without external images."
    },
    {
      title: "City Grid",
      category: "Architecture",
      year: 2024,
      views: 2210,
      art: "vb-gallery-eleven-art-city",
      description: "A CSS city illustration for urban portfolio gallery layouts."
    },
    {
      title: "Product Cube",
      category: "Product",
      year: 2026,
      views: 1510,
      art: "vb-gallery-eleven-art-product",
      description: "A CSS product-style visual for ecommerce gallery sorting."
    },
    {
      title: "Forest Light",
      category: "Nature",
      year: 2022,
      views: 1160,
      art: "vb-gallery-eleven-art-forest",
      description: "A CSS forest scene for a sortable image gallery card."
    }
  ];

  function createCard(item, index) {
    return `
      <article class="vb-gallery-eleven-card" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-eleven-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-eleven-body">
          <h4>${item.title}</h4>
          <p>${item.description}</p>

          <div class="vb-gallery-eleven-meta">
            <small>${item.category}</small>
            <small>${item.year}</small>
            <small>${item.views} views</small>
          </div>
        </div>
      </article>
    `;
  }

  function getSortedItems(sortType) {
    const sortedItems = items.slice();

    if (sortType === "newest") {
      sortedItems.sort(function (a, b) {
        return b.year - a.year;
      });
    }

    if (sortType === "oldest") {
      sortedItems.sort(function (a, b) {
        return a.year - b.year;
      });
    }

    if (sortType === "title") {
      sortedItems.sort(function (a, b) {
        return a.title.localeCompare(b.title);
      });
    }

    if (sortType === "views") {
      sortedItems.sort(function (a, b) {
        return b.views - a.views;
      });
    }

    return sortedItems;
  }

  function renderGallery(sortType) {
    const sortedItems = getSortedItems(sortType);
    grid.innerHTML = sortedItems.map(createCard).join("");

    const label = sortType.replace("-", " ");
    status.textContent = "Sorted by " + label;
  }

  controls.addEventListener("click", function (event) {
    const button = event.target.closest("button[data-sort]");
    if (!button) return;

    controls.querySelectorAll("button").forEach(function (controlButton) {
      controlButton.classList.remove("is-active");
    });

    button.classList.add("is-active");
    renderGallery(button.getAttribute("data-sort"));
  });

  renderGallery("newest");
})();

HTML

<div class="vb-gallery-eleven-demo">
  <div class="vb-gallery-eleven-shell" data-vb-gallery-eleven>
    <div class="vb-gallery-eleven-top">
      <div>
        <span>Example 11</span>
        <h3>Sortable Image Gallery</h3>
        <p>Sort a CSS-illustrated image gallery by newest, oldest, title, or most viewed using JavaScript.</p>
      </div>

      <strong data-vb-gallery-eleven-status>Sorted by newest</strong>
    </div>

    <div class="vb-gallery-eleven-controls" data-vb-gallery-eleven-controls>
      <button type="button" class="is-active" data-sort="newest">Newest</button>
      <button type="button" data-sort="oldest">Oldest</button>
      <button type="button" data-sort="title">Title A-Z</button>
      <button type="button" data-sort="views">Most Viewed</button>
    </div>

    <div class="vb-gallery-eleven-grid" data-vb-gallery-eleven-grid></div>
  </div>
</div>

CSS

.vb-gallery-eleven-demo,
.vb-gallery-eleven-demo * {
  box-sizing: border-box;
}

.vb-gallery-eleven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 14% 14%, rgba(99, 102, 241, 0.16), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(20, 184, 166, 0.16), transparent 34%),
    linear-gradient(135deg, #eef2ff 0%, #ecfeff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(99, 102, 241, 0.22);
  box-shadow: 0 28px 80px rgba(67, 56, 202, 0.10);
}

.vb-gallery-eleven-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-eleven-top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 22px;
}

.vb-gallery-eleven-top span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #e0e7ff;
  color: #3730a3 !important;
  -webkit-text-fill-color: #3730a3 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-eleven-top h3 {
  max-width: 760px;
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-eleven-top p {
  max-width: 720px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-eleven-top strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-eleven-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 22px;
  padding: 12px;
  border-radius: 24px;
  background: rgba(255,255,255,0.76);
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.08);
}

.vb-gallery-eleven-controls button {
  appearance: none;
  border: 1px solid rgba(99, 102, 241, 0.20);
  border-radius: 999px;
  background: #ffffff;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  padding: 11px 15px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  transition: transform 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

.vb-gallery-eleven-controls button:hover {
  transform: translateY(-2px);
  border-color: rgba(99, 102, 241, 0.54);
}

.vb-gallery-eleven-controls button.is-active {
  border-color: rgba(99, 102, 241, 0.88);
  background: linear-gradient(135deg, #4f46e5, #0f766e);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  box-shadow: 0 14px 34px rgba(79, 70, 229, 0.26);
}

.vb-gallery-eleven-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-gallery-eleven-card {
  overflow: hidden;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGalleryElevenIn 0.32s ease both;
}

@keyframes vbGalleryElevenIn {
  from {
    opacity: 0;
    transform: translateY(14px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-eleven-art {
  position: relative;
  height: 240px;
  overflow: hidden;
  background: #0f172a;
}

.vb-gallery-eleven-art::before,
.vb-gallery-eleven-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-eleven-art span {
  position: absolute;
  pointer-events: none;
}

.vb-gallery-eleven-art-aurora {
  background: linear-gradient(135deg, #020617 0%, #1e1b4b 48%, #0f766e 100%);
}

.vb-gallery-eleven-art-aurora::before {
  left: -12%;
  top: 18%;
  width: 124%;
  height: 42%;
  border-radius: 999px;
  background: linear-gradient(90deg, rgba(45,212,191,0), rgba(45,212,191,0.78), rgba(168,85,247,0.62), rgba(45,212,191,0));
  filter: blur(20px);
  transform: rotate(-9deg);
}

.vb-gallery-eleven-art-aurora::after {
  right: 12%;
  top: 18%;
  width: 90px;
  height: 90px;
  border-radius: 999px;
  background: rgba(224, 242, 254, 0.90);
  box-shadow: 0 0 70px rgba(224, 242, 254, 0.62);
}

.vb-gallery-eleven-art-desert {
  background: linear-gradient(180deg, #fed7aa 0%, #fb923c 54%, #7c2d12 100%);
}

.vb-gallery-eleven-art-desert::before {
  left: -10%;
  right: -10%;
  bottom: -34%;
  height: 72%;
  border-radius: 50% 50% 0 0;
  background: #431407;
}

.vb-gallery-eleven-art-desert::after {
  right: 18%;
  top: 16%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 54px rgba(255,247,237,0.72);
}

.vb-gallery-eleven-art-ocean {
  background: linear-gradient(180deg, #bae6fd 0%, #0284c7 52%, #082f49 100%);
}

.vb-gallery-eleven-art-ocean::before {
  left: -10%;
  right: -10%;
  bottom: 20%;
  height: 28%;
  background:
    radial-gradient(circle at 12% 40%, rgba(255,255,255,0.86), transparent 11%),
    radial-gradient(circle at 34% 55%, rgba(255,255,255,0.62), transparent 12%),
    radial-gradient(circle at 68% 42%, rgba(255,255,255,0.70), transparent 12%),
    linear-gradient(90deg, rgba(255,255,255,0.12), rgba(255,255,255,0.50), rgba(255,255,255,0.12));
  filter: blur(2px);
  transform: rotate(-2deg);
}

.vb-gallery-eleven-art-ocean::after {
  left: 16%;
  top: 18%;
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background: rgba(255,255,255,0.42);
  filter: blur(10px);
}

.vb-gallery-eleven-art-city {
  background: linear-gradient(180deg, #312e81 0%, #111827 66%, #020617 100%);
}

.vb-gallery-eleven-art-city::before {
  left: 10%;
  bottom: 0;
  width: 18%;
  height: 58%;
  background: #020617;
  box-shadow:
    70px -42px 0 #020617,
    142px -12px 0 #020617,
    224px -62px 0 #020617,
    304px -24px 0 #020617;
}

.vb-gallery-eleven-art-city::after {
  inset: 18% 10% auto 10%;
  height: 3px;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,0.82) 0 8px, transparent 8px 24px);
  box-shadow:
    0 34px 0 rgba(255,255,255,0.42),
    0 70px 0 rgba(255,255,255,0.22);
}

.vb-gallery-eleven-art-product {
  background: radial-gradient(circle at 50% 40%, #ffffff 0%, #e0e7ff 34%, #312e81 100%);
}

.vb-gallery-eleven-art-product::before {
  left: 50%;
  top: 50%;
  width: 118px;
  height: 118px;
  border-radius: 32px;
  background: linear-gradient(135deg, #ffffff, #c7d2fe);
  box-shadow: 0 28px 70px rgba(49,46,129,0.32);
  transform: translate(-50%, -50%) rotate(12deg);
}

.vb-gallery-eleven-art-product::after {
  left: 50%;
  top: 50%;
  width: 74px;
  height: 74px;
  border-radius: 22px;
  border: 4px solid rgba(79,70,229,0.46);
  transform: translate(-50%, -50%) rotate(12deg);
}

.vb-gallery-eleven-art-forest {
  background: linear-gradient(180deg, #bbf7d0 0%, #16a34a 54%, #052e16 100%);
}

.vb-gallery-eleven-art-forest::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 44% 100%, transparent 0 25%, #064e3b 0 50%, transparent 0),
    conic-gradient(from 45deg at 72% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-eleven-art-forest::after {
  left: 12%;
  top: 16%;
  width: 70px;
  height: 70px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-eleven-body {
  display: grid;
  gap: 10px;
  padding: 18px;
}

.vb-gallery-eleven-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-eleven-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-eleven-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-top: 10px;
  border-top: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-gallery-eleven-meta small {
  display: inline-flex;
  padding: 6px 9px;
  border-radius: 999px;
  background: #f1f5f9;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 11px;
  font-weight: 900;
}

@media (max-width: 980px) {
  .vb-gallery-eleven-top {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-eleven-top strong {
    justify-content: center;
    width: 100%;
  }

  .vb-gallery-eleven-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 620px) {
  .vb-gallery-eleven-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-eleven-top h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-eleven-grid {
    grid-template-columns: 1fr;
  }

  .vb-gallery-eleven-controls {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-eleven-controls button {
    width: 100%;
  }
}

This sortable image gallery is useful for portfolio archives, product collections, media libraries, design inspiration pages, photography collections, and any image gallery where users should be able to change the order of visual content.

12. Favorite Images Gallery

A favorite images gallery lets users save selected images and return to them later. This is useful for product wishlists, design inspiration boards, portfolio collections, real estate galleries, interior design references, travel planning pages, and any visual browsing experience where users may want to remember specific items.

This example uses JavaScript localStorage logic. Users can click the heart button on any CSS-illustrated card, and the favorite state remains saved after page refresh. The gallery can also switch between all images and only favorite images.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twelve]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-twelve-grid]");
  const count = gallery.querySelector("[data-vb-gallery-twelve-count]");
  const empty = gallery.querySelector("[data-vb-gallery-twelve-empty]");
  const viewButtons = gallery.querySelectorAll("[data-view]");
  const storageKey = "vb-gallery-twelve-favorites";

  const items = [
    {
      id: "rose-shape",
      title: "Rose Shape",
      category: "Abstract",
      art: "vb-gallery-twelve-art-rose",
      description: "A CSS-only abstract rose illustration saved as a favorite gallery item."
    },
    {
      id: "canyon-light",
      title: "Canyon Light",
      category: "Travel",
      art: "vb-gallery-twelve-art-canyon",
      description: "A CSS canyon-style thumbnail for travel and inspiration galleries."
    },
    {
      id: "blue-lagoon",
      title: "Blue Lagoon",
      category: "Nature",
      art: "vb-gallery-twelve-art-lagoon",
      description: "A CSS water-inspired illustration for visual collection cards."
    },
    {
      id: "night-peaks",
      title: "Night Peaks",
      category: "Landscape",
      art: "vb-gallery-twelve-art-night",
      description: "A CSS night mountain scene for saved image gallery examples."
    },
    {
      id: "orbit-object",
      title: "Orbit Object",
      category: "Product",
      art: "vb-gallery-twelve-art-orbit",
      description: "A CSS product-style orbit illustration for favorite image cards."
    },
    {
      id: "green-meadow",
      title: "Green Meadow",
      category: "Nature",
      art: "vb-gallery-twelve-art-meadow",
      description: "A CSS meadow illustration for a localStorage favorite gallery."
    }
  ];

  let activeView = "all";
  let favorites = getFavorites();

  function getFavorites() {
    try {
      return JSON.parse(localStorage.getItem(storageKey)) || [];
    } catch (error) {
      return [];
    }
  }

  function saveFavorites() {
    localStorage.setItem(storageKey, JSON.stringify(favorites));
  }

  function isFavorite(id) {
    return favorites.includes(id);
  }

  function toggleFavorite(id) {
    if (isFavorite(id)) {
      favorites = favorites.filter(function (favoriteId) {
        return favoriteId !== id;
      });
    } else {
      favorites.push(id);
    }

    saveFavorites();
    renderGallery();
  }

  function createCard(item, index) {
    const favoriteClass = isFavorite(item.id) ? "is-favorite" : "";
    const heartSymbol = isFavorite(item.id) ? "♥" : "♡";

    return `
      <article class="vb-gallery-twelve-card" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-twelve-art ${item.art}" aria-label="${item.title} illustration" role="img">
          <button type="button" class="vb-gallery-twelve-heart ${favoriteClass}" data-favorite-id="${item.id}" aria-label="Toggle favorite for ${item.title}">${heartSymbol}</button>
        </div>

        <div class="vb-gallery-twelve-body">
          <span class="vb-gallery-twelve-tag">${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function getVisibleItems() {
    if (activeView === "favorites") {
      return items.filter(function (item) {
        return isFavorite(item.id);
      });
    }

    return items;
  }

  function updateCount() {
    const text = favorites.length === 1 ? "1 favorite" : favorites.length + " favorites";
    count.textContent = text;
  }

  function renderGallery() {
    const visibleItems = getVisibleItems();

    grid.innerHTML = visibleItems.map(createCard).join("");
    empty.hidden = !(activeView === "favorites" && visibleItems.length === 0);
    updateCount();
  }

  grid.addEventListener("click", function (event) {
    const button = event.target.closest("[data-favorite-id]");
    if (!button) return;

    toggleFavorite(button.getAttribute("data-favorite-id"));
  });

  viewButtons.forEach(function (button) {
    button.addEventListener("click", function () {
      activeView = button.getAttribute("data-view");

      viewButtons.forEach(function (viewButton) {
        viewButton.classList.remove("is-active");
      });

      button.classList.add("is-active");
      renderGallery();
    });
  });

  renderGallery();
})();

HTML

<div class="vb-gallery-twelve-demo">
  <div class="vb-gallery-twelve-shell" data-vb-gallery-twelve>
    <div class="vb-gallery-twelve-hero">
      <div>
        <span>Example 12</span>
        <h3>Favorite Images Gallery</h3>
        <p>Save favorite gallery items with localStorage and switch between all images and saved favorites.</p>
      </div>

      <div class="vb-gallery-twelve-actions">
        <button type="button" class="is-active" data-view="all">All Images</button>
        <button type="button" data-view="favorites">Favorites Only</button>
        <strong data-vb-gallery-twelve-count>0 favorites</strong>
      </div>
    </div>

    <div class="vb-gallery-twelve-grid" data-vb-gallery-twelve-grid></div>

    <div class="vb-gallery-twelve-empty" data-vb-gallery-twelve-empty hidden>
      <h4>No favorite images yet</h4>
      <p>Click the heart button on a gallery card to save it as a favorite.</p>
    </div>
  </div>
</div>

CSS

.vb-gallery-twelve-demo,
.vb-gallery-twelve-demo * {
  box-sizing: border-box;
}

.vb-gallery-twelve-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px 18px 44px 18px;
  background:
    radial-gradient(circle at 14% 14%, rgba(236, 72, 153, 0.18), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(251, 146, 60, 0.16), transparent 34%),
    linear-gradient(135deg, #fdf2f8 0%, #fff7ed 52%, #ffffff 100%) !important;
  border: 1px solid rgba(244, 114, 182, 0.24);
  box-shadow: 0 28px 80px rgba(190, 24, 93, 0.10);
}

.vb-gallery-twelve-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-twelve-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 390px);
  gap: 20px;
  align-items: stretch;
  margin-bottom: 22px;
}

.vb-gallery-twelve-hero > div:first-child {
  padding: clamp(22px, 4vw, 34px);
  border-radius: 34px 10px 34px 10px;
  background:
    radial-gradient(circle at 16% 18%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, #831843, #be123c 52%, #9a3412) !important;
  box-shadow: 0 24px 70px rgba(190, 24, 93, 0.22);
}

.vb-gallery-twelve-hero span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fce7f3 !important;
  -webkit-text-fill-color: #fce7f3 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-twelve-hero h3 {
  max-width: 760px;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-twelve-hero p {
  max-width: 720px;
  margin: 0 !important;
  color: #ffe4e6 !important;
  -webkit-text-fill-color: #ffe4e6 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-twelve-actions {
  display: grid;
  align-content: center;
  gap: 12px;
  padding: 22px;
  border-radius: 10px 34px 10px 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.09);
}

.vb-gallery-twelve-actions button {
  appearance: none;
  border: 1px solid rgba(244, 114, 182, 0.22);
  border-radius: 999px;
  background: #fdf2f8;
  color: #9d174d !important;
  -webkit-text-fill-color: #9d174d !important;
  padding: 13px 16px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-gallery-twelve-actions button.is-active {
  background: linear-gradient(135deg, #db2777, #f97316);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  border-color: transparent;
  box-shadow: 0 14px 34px rgba(219, 39, 119, 0.26);
}

.vb-gallery-twelve-actions strong {
  display: flex;
  justify-content: center;
  padding: 12px 14px;
  border-radius: 18px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-gallery-twelve-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-gallery-twelve-card {
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGalleryTwelveIn 0.32s ease both;
}

@keyframes vbGalleryTwelveIn {
  from {
    opacity: 0;
    transform: translateY(14px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.vb-gallery-twelve-art {
  position: relative;
  height: 250px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-twelve-art::before,
.vb-gallery-twelve-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-twelve-art-rose {
  background: linear-gradient(135deg, #500724 0%, #be123c 52%, #fb7185 100%);
}

.vb-gallery-twelve-art-rose::before {
  left: 50%;
  top: 52%;
  width: 150px;
  height: 150px;
  border-radius: 42% 58% 62% 38%;
  background: radial-gradient(circle at 35% 30%, #fff1f2, #fb7185 44%, #9f1239 100%);
  transform: translate(-50%, -50%) rotate(22deg);
  box-shadow: 0 26px 70px rgba(80,7,36,0.36);
}

.vb-gallery-twelve-art-rose::after {
  left: 14%;
  top: 16%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: rgba(255,255,255,0.22);
  filter: blur(6px);
}

.vb-gallery-twelve-art-canyon {
  background: linear-gradient(180deg, #fed7aa 0%, #f97316 50%, #7c2d12 100%);
}

.vb-gallery-twelve-art-canyon::before {
  left: -8%;
  right: -8%;
  bottom: -22%;
  height: 66%;
  border-radius: 50% 50% 0 0;
  background: #431407;
}

.vb-gallery-twelve-art-canyon::after {
  left: 16%;
  top: 16%;
  width: 78px;
  height: 78px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 62px rgba(255,247,237,0.72);
}

.vb-gallery-twelve-art-lagoon {
  background: linear-gradient(180deg, #cffafe 0%, #06b6d4 48%, #164e63 100%);
}

.vb-gallery-twelve-art-lagoon::before {
  left: -6%;
  right: -6%;
  bottom: 26%;
  height: 26%;
  background:
    radial-gradient(circle at 16% 52%, rgba(255,255,255,0.88), transparent 12%),
    radial-gradient(circle at 46% 38%, rgba(255,255,255,0.62), transparent 13%),
    radial-gradient(circle at 78% 58%, rgba(255,255,255,0.72), transparent 14%);
  filter: blur(2px);
}

.vb-gallery-twelve-art-lagoon::after {
  right: 14%;
  top: 16%;
  width: 96px;
  height: 96px;
  border-radius: 999px;
  background: rgba(255,255,255,0.28);
  filter: blur(10px);
}

.vb-gallery-twelve-art-night {
  background: linear-gradient(180deg, #111827 0%, #312e81 54%, #020617 100%);
}

.vb-gallery-twelve-art-night::before {
  inset: 18% 12% auto 12%;
  height: 120px;
  background:
    radial-gradient(circle at 10% 20%, #ffffff 0 2px, transparent 3px),
    radial-gradient(circle at 30% 70%, #ffffff 0 2px, transparent 3px),
    radial-gradient(circle at 52% 32%, #ffffff 0 2px, transparent 3px),
    radial-gradient(circle at 78% 62%, #ffffff 0 2px, transparent 3px),
    radial-gradient(circle at 90% 20%, #ffffff 0 2px, transparent 3px);
}

.vb-gallery-twelve-art-night::after {
  left: 12%;
  right: 12%;
  bottom: 0;
  height: 40%;
  background: #020617;
  clip-path: polygon(0 100%, 0 55%, 12% 70%, 24% 42%, 36% 62%, 48% 30%, 60% 58%, 74% 36%, 88% 64%, 100% 48%, 100% 100%);
}

.vb-gallery-twelve-art-orbit {
  background: radial-gradient(circle at 50% 50%, #ffffff 0%, #c4b5fd 34%, #581c87 100%);
}

.vb-gallery-twelve-art-orbit::before {
  left: 50%;
  top: 50%;
  width: 134px;
  height: 134px;
  border-radius: 999px;
  border: 16px solid rgba(255,255,255,0.36);
  border-top-color: rgba(255,255,255,0.86);
  transform: translate(-50%, -50%) rotate(-18deg);
}

.vb-gallery-twelve-art-orbit::after {
  left: 50%;
  top: 50%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: linear-gradient(135deg, #ffffff, #a78bfa);
  transform: translate(-50%, -50%);
  box-shadow: 0 28px 70px rgba(88,28,135,0.32);
}

.vb-gallery-twelve-art-meadow {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 56%, #052e16 100%);
}

.vb-gallery-twelve-art-meadow::before {
  left: -10%;
  right: -10%;
  bottom: -18%;
  height: 56%;
  border-radius: 50% 50% 0 0;
  background: #166534;
}

.vb-gallery-twelve-art-meadow::after {
  left: 16%;
  top: 14%;
  width: 84px;
  height: 84px;
  border-radius: 999px;
  background: #f0fdf4;
  box-shadow: 0 0 56px rgba(240,253,244,0.72);
}

.vb-gallery-twelve-heart {
  position: absolute;
  z-index: 3;
  top: 14px;
  right: 14px;
  width: 46px;
  height: 46px;
  border: 1px solid rgba(255,255,255,0.24);
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.58);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(10px);
  transition: transform 0.2s ease, background 0.2s ease;
}

.vb-gallery-twelve-heart:hover {
  transform: scale(1.06);
}

.vb-gallery-twelve-heart.is-favorite {
  background: linear-gradient(135deg, #db2777, #f97316);
  border-color: transparent;
}

.vb-gallery-twelve-body {
  display: grid;
  gap: 10px;
  padding: 18px;
}

.vb-gallery-twelve-body h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-twelve-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-twelve-tag {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #fdf2f8;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-twelve-empty {
  margin-top: 18px;
  padding: 26px;
  border-radius: 26px;
  background: #ffffff;
  border: 1px dashed rgba(244, 114, 182, 0.44);
  text-align: center;
}

.vb-gallery-twelve-empty h4 {
  margin: 0 0 8px !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 24px !important;
  font-weight: 950 !important;
}

.vb-gallery-twelve-empty p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 15px;
  line-height: 1.6;
}

@media (max-width: 980px) {
  .vb-gallery-twelve-hero {
    grid-template-columns: 1fr;
  }

  .vb-gallery-twelve-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 620px) {
  .vb-gallery-twelve-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-twelve-hero h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-twelve-grid {
    grid-template-columns: 1fr;
  }
}

This favorite images gallery is useful for visual wishlists, product inspiration boards, design references, travel planning galleries, real estate favorites, portfolio collections, and any image gallery where visitors should be able to save selected items locally.

13. Selectable Image Gallery

A selectable image gallery lets users select multiple images and track their selected items. This pattern is useful for media managers, product comparison galleries, moodboards, portfolio review tools, image approval systems, real estate shortlist pages, and design selection interfaces.

This example uses JavaScript selection state management. Users can click gallery cards to select or unselect them, the selected count updates instantly, selected cards get a clear visual state, and the Clear Selection button resets the gallery. The thumbnail artwork is CSS-only, so the example does not use external image files.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-thirteen]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-thirteen-grid]");
  const count = gallery.querySelector("[data-vb-gallery-thirteen-count]");
  const clearButton = gallery.querySelector("[data-vb-gallery-thirteen-clear]");

  const items = [
    {
      id: "horizon",
      title: "Blue Horizon",
      category: "Landscape",
      art: "vb-gallery-thirteen-art-horizon",
      description: "A selectable CSS landscape thumbnail for visual review interfaces."
    },
    {
      id: "gradient",
      title: "Gradient Poster",
      category: "Abstract",
      art: "vb-gallery-thirteen-art-gradient",
      description: "A bold CSS abstract image card for creative selection galleries."
    },
    {
      id: "layers",
      title: "Green Layers",
      category: "Nature",
      art: "vb-gallery-thirteen-art-layers",
      description: "A CSS nature-inspired thumbnail for image selection systems."
    },
    {
      id: "cards",
      title: "Product Stack",
      category: "Product",
      art: "vb-gallery-thirteen-art-cards",
      description: "A CSS product-style visual for choosing campaign assets."
    },
    {
      id: "sunset",
      title: "Sunset Peaks",
      category: "Travel",
      art: "vb-gallery-thirteen-art-sunset",
      description: "A warm CSS travel thumbnail for shortlist gallery layouts."
    },
    {
      id: "mono",
      title: "Mono Orbit",
      category: "Editorial",
      art: "vb-gallery-thirteen-art-mono",
      description: "A monochrome CSS illustration for editorial gallery selection."
    }
  ];

  let selectedIds = [];

  function isSelected(id) {
    return selectedIds.includes(id);
  }

  function updateCount() {
    count.textContent = selectedIds.length === 1 ? "1 selected" : selectedIds.length + " selected";
  }

  function createCard(item, index) {
    const selectedClass = isSelected(item.id) ? "is-selected" : "";
    const checkSymbol = isSelected(item.id) ? "✓" : "+";

    return `
      <article class="vb-gallery-thirteen-card ${selectedClass}" data-select-id="${item.id}" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-thirteen-check">${checkSymbol}</div>
        <div class="vb-gallery-thirteen-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-thirteen-body">
          <span class="vb-gallery-thirteen-tag">${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function renderGallery() {
    grid.innerHTML = items.map(createCard).join("");
    updateCount();
  }

  function toggleSelection(id) {
    if (isSelected(id)) {
      selectedIds = selectedIds.filter(function (selectedId) {
        return selectedId !== id;
      });
    } else {
      selectedIds.push(id);
    }

    renderGallery();
  }

  grid.addEventListener("click", function (event) {
    const card = event.target.closest("[data-select-id]");
    if (!card) return;

    toggleSelection(card.getAttribute("data-select-id"));
  });

  clearButton.addEventListener("click", function () {
    selectedIds = [];
    renderGallery();
  });

  renderGallery();
})();

HTML

<div class="vb-gallery-thirteen-demo">
  <div class="vb-gallery-thirteen-shell" data-vb-gallery-thirteen>
    <div class="vb-gallery-thirteen-header">
      <div>
        <span>Example 13</span>
        <h3>Selectable Image Gallery</h3>
        <p>Select multiple CSS-illustrated gallery cards and manage the selected image count with JavaScript.</p>
      </div>

      <div class="vb-gallery-thirteen-summary">
        <strong data-vb-gallery-thirteen-count>0 selected</strong>
        <button type="button" data-vb-gallery-thirteen-clear>Clear Selection</button>
      </div>
    </div>

    <div class="vb-gallery-thirteen-grid" data-vb-gallery-thirteen-grid></div>
  </div>
</div>

CSS

.vb-gallery-thirteen-demo,
.vb-gallery-thirteen-demo * {
  box-sizing: border-box;
}

.vb-gallery-thirteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 14% 12%, rgba(59, 130, 246, 0.16), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(16, 185, 129, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfdf5 52%, #ffffff 100%) !important;
  border: 1px solid rgba(59, 130, 246, 0.22);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-gallery-thirteen-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-thirteen-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
  gap: 20px;
  align-items: stretch;
  margin-bottom: 22px;
}

.vb-gallery-thirteen-header > div:first-child {
  padding: clamp(22px, 4vw, 34px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 14% 18%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, #0f172a, #1d4ed8 54%, #0f766e) !important;
  box-shadow: 0 24px 70px rgba(30, 64, 175, 0.22);
}

.vb-gallery-thirteen-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-thirteen-header h3 {
  max-width: 780px;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-thirteen-header p {
  max-width: 720px;
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-thirteen-summary {
  display: grid;
  align-content: center;
  gap: 12px;
  padding: 22px;
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.09);
}

.vb-gallery-thirteen-summary strong {
  display: flex;
  justify-content: center;
  padding: 14px 16px;
  border-radius: 20px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
}

.vb-gallery-thirteen-summary button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #0f766e);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 13px 16px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(37, 99, 235, 0.24);
}

.vb-gallery-thirteen-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-thirteen-card {
  position: relative;
  overflow: hidden;
  min-height: 330px;
  border: 2px solid transparent;
  border-radius: 30px;
  background: #ffffff;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  cursor: pointer;
  transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease;
}

.vb-gallery-thirteen-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 26px 64px rgba(15, 23, 42, 0.16);
}

.vb-gallery-thirteen-card.is-selected {
  border-color: rgba(37, 99, 235, 0.86);
  box-shadow: 0 24px 70px rgba(37, 99, 235, 0.22);
}

.vb-gallery-thirteen-check {
  position: absolute;
  z-index: 5;
  top: 14px;
  right: 14px;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.62);
  border: 1px solid rgba(255,255,255,0.24);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  font-weight: 950;
  backdrop-filter: blur(10px);
  transition: background 0.22s ease, transform 0.22s ease;
}

.vb-gallery-thirteen-card.is-selected .vb-gallery-thirteen-check {
  background: linear-gradient(135deg, #2563eb, #0f766e);
  transform: scale(1.06);
}

.vb-gallery-thirteen-art {
  position: relative;
  height: 215px;
  overflow: hidden;
  background: #0f172a;
}

.vb-gallery-thirteen-art::before,
.vb-gallery-thirteen-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-thirteen-art-horizon {
  background: linear-gradient(180deg, #bfdbfe 0%, #60a5fa 45%, #1e3a8a 100%);
}

.vb-gallery-thirteen-art-horizon::before {
  left: -10%;
  right: -10%;
  bottom: -28%;
  height: 58%;
  border-radius: 50% 50% 0 0;
  background: #0f172a;
}

.vb-gallery-thirteen-art-horizon::after {
  right: 18%;
  top: 16%;
  width: 76px;
  height: 76px;
  border-radius: 999px;
  background: rgba(255,255,255,0.88);
  box-shadow: 0 0 58px rgba(255,255,255,0.66);
}

.vb-gallery-thirteen-art-gradient {
  background: linear-gradient(135deg, #581c87 0%, #db2777 50%, #fb7185 100%);
}

.vb-gallery-thirteen-art-gradient::before {
  left: 50%;
  top: 50%;
  width: 150px;
  height: 150px;
  border-radius: 34px;
  background: rgba(255,255,255,0.18);
  border: 1px solid rgba(255,255,255,0.30);
  transform: translate(-50%, -50%) rotate(18deg);
  backdrop-filter: blur(4px);
}

.vb-gallery-thirteen-art-gradient::after {
  left: 18%;
  top: 18%;
  width: 90px;
  height: 90px;
  border-radius: 999px;
  background: rgba(255,255,255,0.18);
  filter: blur(8px);
}

.vb-gallery-thirteen-art-layers {
  background: linear-gradient(180deg, #ccfbf1 0%, #14b8a6 48%, #134e4a 100%);
}

.vb-gallery-thirteen-art-layers::before {
  left: -12%;
  right: -12%;
  bottom: 0;
  height: 54%;
  background:
    radial-gradient(ellipse at 20% 100%, #042f2e 0 38%, transparent 39%),
    radial-gradient(ellipse at 58% 100%, #115e59 0 42%, transparent 43%),
    radial-gradient(ellipse at 92% 100%, #042f2e 0 38%, transparent 39%);
}

.vb-gallery-thirteen-art-layers::after {
  left: 16%;
  top: 18%;
  width: 84px;
  height: 84px;
  border-radius: 999px;
  background: rgba(240,253,250,0.82);
  filter: blur(5px);
}

.vb-gallery-thirteen-art-cards {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #dbeafe 38%, #1d4ed8 100%);
}

.vb-gallery-thirteen-art-cards::before {
  left: 50%;
  top: 50%;
  width: 118px;
  height: 148px;
  border-radius: 26px;
  background: linear-gradient(135deg, #ffffff, #bfdbfe);
  box-shadow:
    -34px 18px 0 rgba(255,255,255,0.34),
    34px -18px 0 rgba(255,255,255,0.24),
    0 26px 70px rgba(30,64,175,0.32);
  transform: translate(-50%, -50%) rotate(8deg);
}

.vb-gallery-thirteen-art-cards::after {
  left: 50%;
  top: 50%;
  width: 72px;
  height: 12px;
  border-radius: 999px;
  background: #2563eb;
  box-shadow: 0 26px 0 rgba(37,99,235,0.42);
  transform: translate(-50%, -50%) rotate(8deg);
}

.vb-gallery-thirteen-art-sunset {
  background: linear-gradient(180deg, #fde68a 0%, #fb923c 46%, #7f1d1d 100%);
}

.vb-gallery-thirteen-art-sunset::before {
  left: -6%;
  right: -6%;
  bottom: -18%;
  height: 54%;
  background: #431407;
  clip-path: polygon(0 100%, 0 50%, 12% 62%, 24% 38%, 38% 66%, 52% 34%, 66% 62%, 82% 42%, 100% 56%, 100% 100%);
}

.vb-gallery-thirteen-art-sunset::after {
  left: 18%;
  top: 18%;
  width: 88px;
  height: 88px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 70px rgba(255,247,237,0.72);
}

.vb-gallery-thirteen-art-mono {
  background: linear-gradient(135deg, #020617 0%, #334155 52%, #cbd5e1 100%);
}

.vb-gallery-thirteen-art-mono::before {
  left: 18%;
  top: 20%;
  width: 180px;
  height: 180px;
  border-radius: 999px;
  border: 22px solid rgba(255,255,255,0.22);
  border-right-color: rgba(255,255,255,0.62);
  transform: rotate(-18deg);
}

.vb-gallery-thirteen-art-mono::after {
  right: 12%;
  bottom: 12%;
  width: 130px;
  height: 48px;
  border-radius: 999px;
  background: rgba(255,255,255,0.22);
  filter: blur(4px);
}

.vb-gallery-thirteen-body {
  display: grid;
  gap: 8px;
  padding: 18px;
}

.vb-gallery-thirteen-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-thirteen-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-thirteen-tag {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #eff6ff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 11px;
  font-weight: 950;
}

@media (max-width: 1050px) {
  .vb-gallery-thirteen-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 820px) {
  .vb-gallery-thirteen-header {
    grid-template-columns: 1fr;
  }

  .vb-gallery-thirteen-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .vb-gallery-thirteen-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-thirteen-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-thirteen-grid {
    grid-template-columns: 1fr;
  }
}

This selectable image gallery is useful for media management tools, product comparison pages, image approval workflows, real estate shortlist galleries, design moodboards, portfolio review pages, and interactive visual selection interfaces.

14. Image Gallery with Tabs

An image gallery with tabs organizes multiple image collections inside one compact interface. This is useful for portfolios, product collections, team galleries, event albums, service pages, case studies, travel websites, and visual landing page sections where different groups of images should be separated clearly.

This example uses JavaScript tab state and collection switching. Each tab loads a different CSS-illustrated gallery collection, the active tab state updates, and the visible cards re-render from the selected collection data. No external images are used.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-fourteen]");
  if (!gallery) return;

  const tabs = gallery.querySelector("[data-vb-gallery-fourteen-tabs]");
  const grid = gallery.querySelector("[data-vb-gallery-fourteen-grid]");
  const title = gallery.querySelector("[data-vb-gallery-fourteen-title]");
  const count = gallery.querySelector("[data-vb-gallery-fourteen-count]");

  const collections = {
    portfolio: {
      title: "Portfolio Collection",
      items: [
        {
          title: "Website Concept",
          category: "Portfolio",
          art: "vb-gallery-fourteen-art-portfolio-one",
          description: "A CSS-only portfolio image for web design project galleries."
        },
        {
          title: "Brand System",
          category: "Portfolio",
          art: "vb-gallery-fourteen-art-portfolio-two",
          description: "A CSS visual card for branding and case study collections."
        },
        {
          title: "Landing Page",
          category: "Portfolio",
          art: "vb-gallery-fourteen-art-portfolio-three",
          description: "A CSS illustration for modern landing page project previews."
        }
      ]
    },
    products: {
      title: "Product Collection",
      items: [
        {
          title: "Package Design",
          category: "Product",
          art: "vb-gallery-fourteen-art-product-one",
          description: "A CSS product-style thumbnail for ecommerce gallery tabs."
        },
        {
          title: "Soft Display",
          category: "Product",
          art: "vb-gallery-fourteen-art-product-two",
          description: "A CSS product scene for shop and product launch galleries."
        },
        {
          title: "Glass Object",
          category: "Product",
          art: "vb-gallery-fourteen-art-product-three",
          description: "A CSS glassmorphism product card for visual product collections."
        }
      ]
    },
    travel: {
      title: "Travel Collection",
      items: [
        {
          title: "Warm Canyon",
          category: "Travel",
          art: "vb-gallery-fourteen-art-travel-one",
          description: "A CSS canyon illustration for destination gallery sections."
        },
        {
          title: "Blue Coast",
          category: "Travel",
          art: "vb-gallery-fourteen-art-travel-two",
          description: "A CSS water-inspired card for travel image collections."
        },
        {
          title: "Forest Route",
          category: "Travel",
          art: "vb-gallery-fourteen-art-travel-three",
          description: "A CSS forest card for outdoor and travel galleries."
        }
      ]
    },
    events: {
      title: "Event Collection",
      items: [
        {
          title: "Stage Lights",
          category: "Event",
          art: "vb-gallery-fourteen-art-event-one",
          description: "A CSS stage-inspired image card for event gallery tabs."
        },
        {
          title: "Night Venue",
          category: "Event",
          art: "vb-gallery-fourteen-art-event-two",
          description: "A CSS venue card for conference and live event galleries."
        },
        {
          title: "Circle Motion",
          category: "Event",
          art: "vb-gallery-fourteen-art-event-three",
          description: "A CSS motion-style event thumbnail for visual recaps."
        }
      ]
    }
  };

  function createCard(item, index) {
    return `
      <article class="vb-gallery-fourteen-card" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-fourteen-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-fourteen-body">
          <span class="vb-gallery-fourteen-tag">${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function renderCollection(collectionKey) {
    const collection = collections[collectionKey];

    title.textContent = collection.title;
    count.textContent = collection.items.length + " items";
    grid.innerHTML = collection.items.map(createCard).join("");
  }

  tabs.addEventListener("click", function (event) {
    const button = event.target.closest("button[data-tab]");
    if (!button) return;

    tabs.querySelectorAll("button").forEach(function (tabButton) {
      tabButton.classList.remove("is-active");
    });

    button.classList.add("is-active");
    renderCollection(button.getAttribute("data-tab"));
  });

  renderCollection("portfolio");
})();

HTML

<div class="vb-gallery-fourteen-demo">
  <div class="vb-gallery-fourteen-shell" data-vb-gallery-fourteen>
    <div class="vb-gallery-fourteen-intro">
      <span>Example 14</span>
      <h3>Image Gallery with Tabs</h3>
      <p>Switch between different CSS-illustrated gallery collections using JavaScript tab state.</p>
    </div>

    <div class="vb-gallery-fourteen-tabs" role="tablist" data-vb-gallery-fourteen-tabs>
      <button type="button" class="is-active" data-tab="portfolio">Portfolio</button>
      <button type="button" data-tab="products">Products</button>
      <button type="button" data-tab="travel">Travel</button>
      <button type="button" data-tab="events">Events</button>
    </div>

    <div class="vb-gallery-fourteen-status">
      <strong data-vb-gallery-fourteen-title>Portfolio Collection</strong>
      <span data-vb-gallery-fourteen-count>0 items</span>
    </div>

    <div class="vb-gallery-fourteen-grid" data-vb-gallery-fourteen-grid></div>
  </div>
</div>

CSS

.vb-gallery-fourteen-demo,
.vb-gallery-fourteen-demo * {
  box-sizing: border-box;
}

.vb-gallery-fourteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 14px 48px 14px 48px;
  background:
    radial-gradient(circle at 14% 14%, rgba(168, 85, 247, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(34, 211, 238, 0.16), transparent 34%),
    linear-gradient(135deg, #faf5ff 0%, #ecfeff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(168, 85, 247, 0.22);
  box-shadow: 0 28px 80px rgba(88, 28, 135, 0.10);
}

.vb-gallery-fourteen-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-fourteen-intro {
  max-width: 820px;
  margin-bottom: 22px;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 10px 34px 10px 34px;
  background:
    radial-gradient(circle at 16% 18%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, #581c87, #0f172a 58%, #155e75) !important;
  box-shadow: 0 24px 70px rgba(88, 28, 135, 0.24);
}

.vb-gallery-fourteen-intro span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  color: #e9d5ff !important;
  -webkit-text-fill-color: #e9d5ff !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-fourteen-intro h3 {
  max-width: 760px;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-fourteen-intro p {
  max-width: 720px;
  margin: 0 !important;
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-fourteen-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 16px;
  padding: 12px;
  border-radius: 24px;
  background: rgba(255,255,255,0.78);
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.08);
}

.vb-gallery-fourteen-tabs button {
  appearance: none;
  border: 1px solid rgba(168, 85, 247, 0.22);
  border-radius: 999px;
  background: #ffffff;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  padding: 11px 15px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  transition: transform 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

.vb-gallery-fourteen-tabs button:hover {
  transform: translateY(-2px);
  border-color: rgba(168, 85, 247, 0.56);
}

.vb-gallery-fourteen-tabs button.is-active {
  border-color: transparent;
  background: linear-gradient(135deg, #7c3aed, #0891b2);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  box-shadow: 0 14px 34px rgba(124, 58, 237, 0.26);
}

.vb-gallery-fourteen-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 18px;
  padding: 14px 16px;
  border-radius: 22px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.08);
}

.vb-gallery-fourteen-status strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px;
  font-weight: 950;
}

.vb-gallery-fourteen-status span {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 999px;
  background: #f3e8ff;
  color: #6b21a8 !important;
  -webkit-text-fill-color: #6b21a8 !important;
  font-size: 12px;
  font-weight: 950;
}

.vb-gallery-fourteen-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-gallery-fourteen-card {
  overflow: hidden;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGalleryFourteenIn 0.32s ease both;
}

@keyframes vbGalleryFourteenIn {
  from {
    opacity: 0;
    transform: translateY(14px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-fourteen-art {
  position: relative;
  height: 235px;
  overflow: hidden;
  background: #0f172a;
}

.vb-gallery-fourteen-art::before,
.vb-gallery-fourteen-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-fourteen-art-portfolio-one {
  background: linear-gradient(135deg, #020617 0%, #1d4ed8 52%, #38bdf8 100%);
}

.vb-gallery-fourteen-art-portfolio-one::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 62%;
  border-radius: 26px;
  background:
    linear-gradient(135deg, rgba(255,255,255,0.24), rgba(255,255,255,0.04)),
    repeating-linear-gradient(90deg, rgba(255,255,255,0.16) 0 2px, transparent 2px 16px);
  border: 1px solid rgba(255,255,255,0.26);
}

.vb-gallery-fourteen-art-portfolio-one::after {
  right: 18%;
  bottom: 18%;
  width: 84px;
  height: 84px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-fourteen-art-portfolio-two {
  background: radial-gradient(circle at 50% 50%, #ffffff 0%, #ddd6fe 34%, #4c1d95 100%);
}

.vb-gallery-fourteen-art-portfolio-two::before {
  left: 50%;
  top: 50%;
  width: 160px;
  height: 118px;
  border-radius: 28px;
  background: linear-gradient(135deg, #ffffff, #c4b5fd);
  box-shadow: 0 28px 70px rgba(76,29,149,0.32);
  transform: translate(-50%, -50%) rotate(-9deg);
}

.vb-gallery-fourteen-art-portfolio-two::after {
  left: 50%;
  top: 50%;
  width: 88px;
  height: 12px;
  border-radius: 999px;
  background: #7c3aed;
  box-shadow: 0 26px 0 rgba(124,58,237,0.44);
  transform: translate(-50%, -50%) rotate(-9deg);
}

.vb-gallery-fourteen-art-portfolio-three {
  background: linear-gradient(180deg, #f0f9ff 0%, #0ea5e9 52%, #0c4a6e 100%);
}

.vb-gallery-fourteen-art-portfolio-three::before {
  left: -10%;
  right: -10%;
  bottom: -25%;
  height: 60%;
  border-radius: 50% 50% 0 0;
  background: #082f49;
}

.vb-gallery-fourteen-art-portfolio-three::after {
  right: 16%;
  top: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.84);
  box-shadow: 0 0 54px rgba(255,255,255,0.64);
}

.vb-gallery-fourteen-art-product-one {
  background: linear-gradient(135deg, #fef3c7 0%, #f97316 52%, #7c2d12 100%);
}

.vb-gallery-fourteen-art-product-one::before {
  left: 50%;
  top: 50%;
  width: 136px;
  height: 136px;
  border-radius: 30px;
  background: linear-gradient(135deg, #fff7ed, #fed7aa);
  box-shadow: 0 30px 72px rgba(124,45,18,0.34);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-fourteen-art-product-one::after {
  left: 50%;
  top: 50%;
  width: 66px;
  height: 66px;
  border-radius: 18px;
  border: 4px solid rgba(249,115,22,0.42);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-fourteen-art-product-two {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #fecdd3 36%, #be123c 100%);
}

.vb-gallery-fourteen-art-product-two::before {
  left: 50%;
  top: 50%;
  width: 150px;
  height: 100px;
  border-radius: 999px;
  background: linear-gradient(135deg, #ffffff, #fecdd3);
  box-shadow: 0 28px 70px rgba(190,18,60,0.30);
  transform: translate(-50%, -50%);
}

.vb-gallery-fourteen-art-product-two::after {
  left: 44%;
  top: 42%;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  background: #be123c;
  box-shadow: 48px 18px 0 rgba(190,18,60,0.58);
}

.vb-gallery-fourteen-art-product-three {
  background: linear-gradient(135deg, #ecfeff 0%, #06b6d4 48%, #155e75 100%);
}

.vb-gallery-fourteen-art-product-three::before {
  left: 16%;
  top: 18%;
  width: 68%;
  height: 62%;
  border-radius: 28px;
  background: rgba(255,255,255,0.20);
  border: 1px solid rgba(255,255,255,0.34);
  backdrop-filter: blur(4px);
}

.vb-gallery-fourteen-art-product-three::after {
  left: 50%;
  top: 50%;
  width: 92px;
  height: 92px;
  border-radius: 26px;
  background: rgba(255,255,255,0.70);
  transform: translate(-50%, -50%) rotate(20deg);
}

.vb-gallery-fourteen-art-travel-one {
  background: linear-gradient(180deg, #fed7aa 0%, #fb923c 48%, #7c2d12 100%);
}

.vb-gallery-fourteen-art-travel-one::before {
  left: -8%;
  right: -8%;
  bottom: -20%;
  height: 56%;
  background: #431407;
  clip-path: polygon(0 100%, 0 60%, 18% 42%, 34% 66%, 52% 36%, 70% 60%, 86% 38%, 100% 58%, 100% 100%);
}

.vb-gallery-fourteen-art-travel-one::after {
  right: 18%;
  top: 16%;
  width: 78px;
  height: 78px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 62px rgba(255,247,237,0.72);
}

.vb-gallery-fourteen-art-travel-two {
  background: linear-gradient(180deg, #dbeafe 0%, #2563eb 50%, #1e3a8a 100%);
}

.vb-gallery-fourteen-art-travel-two::before {
  left: -8%;
  right: -8%;
  bottom: 24%;
  height: 24%;
  background:
    radial-gradient(circle at 14% 50%, rgba(255,255,255,0.78), transparent 13%),
    radial-gradient(circle at 44% 38%, rgba(255,255,255,0.58), transparent 14%),
    radial-gradient(circle at 76% 58%, rgba(255,255,255,0.70), transparent 14%);
  filter: blur(2px);
}

.vb-gallery-fourteen-art-travel-two::after {
  left: 16%;
  top: 16%;
  width: 96px;
  height: 96px;
  border-radius: 999px;
  background: rgba(255,255,255,0.24);
  filter: blur(10px);
}

.vb-gallery-fourteen-art-travel-three {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-fourteen-art-travel-three::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 70%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-fourteen-art-travel-three::after {
  right: 16%;
  top: 16%;
  width: 78px;
  height: 78px;
  border-radius: 999px;
  background: rgba(240,253,244,0.78);
  filter: blur(4px);
}

.vb-gallery-fourteen-art-event-one {
  background: linear-gradient(135deg, #111827 0%, #7c3aed 52%, #ec4899 100%);
}

.vb-gallery-fourteen-art-event-one::before {
  left: 12%;
  right: 12%;
  top: 22%;
  height: 56%;
  border-radius: 26px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.20) 0 8px, transparent 8px 18px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.22);
}

.vb-gallery-fourteen-art-event-one::after {
  left: 50%;
  top: 50%;
  width: 76px;
  height: 76px;
  border-radius: 999px;
  background: rgba(255,255,255,0.70);
  transform: translate(-50%, -50%);
  box-shadow: 0 0 70px rgba(255,255,255,0.46);
}

.vb-gallery-fourteen-art-event-two {
  background: linear-gradient(180deg, #020617 0%, #1e293b 58%, #0f172a 100%);
}

.vb-gallery-fourteen-art-event-two::before {
  left: 14%;
  right: 14%;
  top: 18%;
  height: 4px;
  background: repeating-linear-gradient(90deg, #ffffff 0 12px, transparent 12px 28px);
  box-shadow:
    0 44px 0 rgba(255,255,255,0.56),
    0 88px 0 rgba(255,255,255,0.30),
    0 132px 0 rgba(255,255,255,0.18);
}

.vb-gallery-fourteen-art-event-two::after {
  right: 15%;
  top: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #facc15;
  box-shadow: 0 0 70px rgba(250,204,21,0.60);
}

.vb-gallery-fourteen-art-event-three {
  background: radial-gradient(circle at 50% 50%, #ffffff 0%, #fecaca 32%, #991b1b 100%);
}

.vb-gallery-fourteen-art-event-three::before {
  left: 50%;
  top: 50%;
  width: 150px;
  height: 150px;
  border-radius: 999px;
  border: 18px solid rgba(255,255,255,0.36);
  border-left-color: rgba(255,255,255,0.86);
  transform: translate(-50%, -50%) rotate(18deg);
}

.vb-gallery-fourteen-art-event-three::after {
  left: 50%;
  top: 50%;
  width: 76px;
  height: 76px;
  border-radius: 999px;
  background: rgba(255,255,255,0.76);
  transform: translate(-50%, -50%);
}

.vb-gallery-fourteen-body {
  display: grid;
  gap: 8px;
  padding: 18px;
}

.vb-gallery-fourteen-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-fourteen-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-fourteen-tag {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #f3e8ff;
  color: #6b21a8 !important;
  -webkit-text-fill-color: #6b21a8 !important;
  font-size: 11px;
  font-weight: 950;
}

@media (max-width: 980px) {
  .vb-gallery-fourteen-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-fourteen-status {
    align-items: flex-start;
    flex-direction: column;
  }
}

@media (max-width: 620px) {
  .vb-gallery-fourteen-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-fourteen-intro h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-fourteen-tabs {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-fourteen-tabs button {
    width: 100%;
  }

  .vb-gallery-fourteen-grid {
    grid-template-columns: 1fr;
  }
}

This image gallery with tabs is useful for portfolio pages, ecommerce product collections, service galleries, event albums, travel collections, case studies, team pages, and landing page sections where several visual categories should stay organized in one compact UI.

15. Image Gallery with Animated Filters

An image gallery with animated filters makes category switching feel smoother than a simple instant hide/show gallery. This pattern is useful for portfolio pages, agency case studies, product collections, design galleries, travel sections, and creative landing pages where visual transitions improve the browsing experience.

This example uses JavaScript to apply a temporary exit animation before re-rendering the filtered gallery. The active category, result count, and animated card entrance are controlled with JavaScript. All thumbnail visuals are CSS-only illustrations, so the demo does not load external images.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-fifteen]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-fifteen-grid]");
  const filters = gallery.querySelector("[data-vb-gallery-fifteen-filters]");
  const count = gallery.querySelector("[data-vb-gallery-fifteen-count]");

  const items = [
    {
      title: "Brand Grid",
      category: "brand",
      label: "Brand",
      art: "vb-gallery-fifteen-art-brand",
      description: "A CSS-only brand visual for animated gallery filtering."
    },
    {
      title: "Warm Route",
      category: "travel",
      label: "Travel",
      art: "vb-gallery-fifteen-art-travel",
      description: "A CSS travel card that appears after filter transitions."
    },
    {
      title: "Product Object",
      category: "product",
      label: "Product",
      art: "vb-gallery-fifteen-art-product",
      description: "A CSS product illustration for animated gallery cards."
    },
    {
      title: "Forest Layer",
      category: "nature",
      label: "Nature",
      art: "vb-gallery-fifteen-art-nature",
      description: "A CSS nature visual for filterable image collections."
    },
    {
      title: "Identity Panel",
      category: "brand",
      label: "Brand",
      art: "vb-gallery-fifteen-art-brand",
      description: "A second brand card used for category filter testing."
    },
    {
      title: "Mountain Trip",
      category: "travel",
      label: "Travel",
      art: "vb-gallery-fifteen-art-travel",
      description: "A second travel card rendered by the animated filter."
    },
    {
      title: "Soft Package",
      category: "product",
      label: "Product",
      art: "vb-gallery-fifteen-art-product",
      description: "A second CSS product card for gallery filtering."
    },
    {
      title: "Green Path",
      category: "nature",
      label: "Nature",
      art: "vb-gallery-fifteen-art-nature",
      description: "A second nature card for animated filter behavior."
    }
  ];

  function createCard(item, index) {
    return `
      <article class="vb-gallery-fifteen-card" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-fifteen-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-fifteen-body">
          <span>${item.label}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function getVisibleItems(filterValue) {
    if (filterValue === "all") {
      return items;
    }

    return items.filter(function (item) {
      return item.category === filterValue;
    });
  }

  function renderGallery(filterValue) {
    const visibleItems = getVisibleItems(filterValue);

    grid.innerHTML = visibleItems.map(createCard).join("");
    count.textContent = visibleItems.length === 1 ? "1 item" : visibleItems.length + " items";
  }

  function animateAndRender(filterValue) {
    grid.classList.add("is-changing");

    window.setTimeout(function () {
      renderGallery(filterValue);
      grid.classList.remove("is-changing");
    }, 220);
  }

  filters.addEventListener("click", function (event) {
    const button = event.target.closest("button[data-filter]");
    if (!button) return;

    filters.querySelectorAll("button").forEach(function (filterButton) {
      filterButton.classList.remove("is-active");
    });

    button.classList.add("is-active");
    animateAndRender(button.getAttribute("data-filter"));
  });

  renderGallery("all");
})();

HTML

<div class="vb-gallery-fifteen-demo">
  <div class="vb-gallery-fifteen-shell" data-vb-gallery-fifteen>
    <div class="vb-gallery-fifteen-head">
      <div>
        <span>Example 15</span>
        <h3>Image Gallery with Animated Filters</h3>
        <p>Filter CSS-illustrated gallery cards with smooth exit and entrance animations.</p>
      </div>

      <strong data-vb-gallery-fifteen-count>0 items</strong>
    </div>

    <div class="vb-gallery-fifteen-filters" data-vb-gallery-fifteen-filters>
      <button type="button" class="is-active" data-filter="all">All</button>
      <button type="button" data-filter="brand">Brand</button>
      <button type="button" data-filter="travel">Travel</button>
      <button type="button" data-filter="product">Product</button>
      <button type="button" data-filter="nature">Nature</button>
    </div>

    <div class="vb-gallery-fifteen-grid" data-vb-gallery-fifteen-grid></div>
  </div>
</div>

CSS

.vb-gallery-fifteen-demo,
.vb-gallery-fifteen-demo * {
  box-sizing: border-box;
}

.vb-gallery-fifteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 36px;
  background:
    radial-gradient(circle at 14% 12%, rgba(245, 158, 11, 0.18), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(236, 72, 153, 0.16), transparent 34%),
    linear-gradient(135deg, #fffbeb 0%, #fdf2f8 52%, #ffffff 100%) !important;
  border: 1px solid rgba(245, 158, 11, 0.24);
  box-shadow: 0 28px 80px rgba(146, 64, 14, 0.10);
}

.vb-gallery-fifteen-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-fifteen-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 20px;
}

.vb-gallery-fifteen-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-fifteen-head h3 {
  max-width: 780px;
  margin: 0 0 14px !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-fifteen-head p {
  max-width: 720px;
  margin: 0 !important;
  color: #4b5563 !important;
  -webkit-text-fill-color: #4b5563 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-fifteen-head strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-fifteen-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 22px;
  padding: 12px;
  border-radius: 24px;
  background: rgba(255,255,255,0.78);
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.08);
}

.vb-gallery-fifteen-filters button {
  appearance: none;
  border: 1px solid rgba(245, 158, 11, 0.26);
  border-radius: 999px;
  background: #ffffff;
  color: #374151 !important;
  -webkit-text-fill-color: #374151 !important;
  padding: 11px 15px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.vb-gallery-fifteen-filters button:hover {
  transform: translateY(-2px);
  border-color: rgba(245, 158, 11, 0.68);
}

.vb-gallery-fifteen-filters button.is-active {
  border-color: transparent;
  background: linear-gradient(135deg, #f59e0b, #db2777);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  box-shadow: 0 14px 34px rgba(219, 39, 119, 0.22);
}

.vb-gallery-fifteen-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-fifteen-grid.is-changing .vb-gallery-fifteen-card {
  animation: vbGalleryFifteenExit 0.22s ease both;
}

@keyframes vbGalleryFifteenExit {
  to {
    opacity: 0;
    transform: translateY(12px) scale(0.96);
  }
}

.vb-gallery-fifteen-card {
  overflow: hidden;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGalleryFifteenEnter 0.36s ease both;
}

@keyframes vbGalleryFifteenEnter {
  from {
    opacity: 0;
    transform: translateY(18px) scale(0.96);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-fifteen-art {
  position: relative;
  height: 230px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-fifteen-art::before,
.vb-gallery-fifteen-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-fifteen-art-brand {
  background: linear-gradient(135deg, #111827 0%, #4f46e5 52%, #38bdf8 100%);
}

.vb-gallery-fifteen-art-brand::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 62%;
  border-radius: 26px;
  background:
    linear-gradient(135deg, rgba(255,255,255,0.24), rgba(255,255,255,0.05)),
    repeating-linear-gradient(90deg, rgba(255,255,255,0.17) 0 3px, transparent 3px 18px);
  border: 1px solid rgba(255,255,255,0.25);
}

.vb-gallery-fifteen-art-brand::after {
  right: 16%;
  bottom: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.28);
  filter: blur(8px);
}

.vb-gallery-fifteen-art-travel {
  background: linear-gradient(180deg, #fde68a 0%, #f97316 50%, #7c2d12 100%);
}

.vb-gallery-fifteen-art-travel::before {
  left: -8%;
  right: -8%;
  bottom: -20%;
  height: 58%;
  background: #431407;
  clip-path: polygon(0 100%, 0 60%, 14% 44%, 28% 66%, 46% 34%, 64% 62%, 82% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-fifteen-art-travel::after {
  right: 18%;
  top: 16%;
  width: 78px;
  height: 78px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 62px rgba(255,247,237,0.72);
}

.vb-gallery-fifteen-art-product {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #fce7f3 36%, #be185d 100%);
}

.vb-gallery-fifteen-art-product::before {
  left: 50%;
  top: 50%;
  width: 132px;
  height: 132px;
  border-radius: 30px;
  background: linear-gradient(135deg, #ffffff, #fbcfe8);
  box-shadow: 0 28px 70px rgba(190,24,93,0.32);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-fifteen-art-product::after {
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  border-radius: 20px;
  border: 4px solid rgba(219,39,119,0.42);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-fifteen-art-nature {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 55%, #052e16 100%);
}

.vb-gallery-fifteen-art-nature::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 70%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-fifteen-art-nature::after {
  right: 16%;
  top: 16%;
  width: 76px;
  height: 76px;
  border-radius: 999px;
  background: rgba(240,253,244,0.84);
  filter: blur(4px);
}

.vb-gallery-fifteen-body {
  display: grid;
  gap: 8px;
  padding: 17px;
}

.vb-gallery-fifteen-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #fffbeb;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-fifteen-body h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-fifteen-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

@media (max-width: 1050px) {
  .vb-gallery-fifteen-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 820px) {
  .vb-gallery-fifteen-head {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-fifteen-head strong {
    justify-content: center;
    width: 100%;
  }

  .vb-gallery-fifteen-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .vb-gallery-fifteen-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-fifteen-head h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-fifteen-filters {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-fifteen-filters button {
    width: 100%;
  }

  .vb-gallery-fifteen-grid {
    grid-template-columns: 1fr;
  }
}

This image gallery with animated filters is useful for portfolio websites, agency galleries, product collections, design showcases, travel galleries, and visual landing pages where category filtering should feel smooth and professional.

16. Fullscreen Image Viewer Gallery

A fullscreen image viewer gallery gives users a focused, distraction-free way to inspect a selected gallery item. It is useful for portfolios, presentation galleries, product previews, case studies, creative collections, and photography-style image sections.

This example uses JavaScript viewer state. Clicking a CSS-illustrated gallery card opens a fullscreen-style overlay, injects the selected item data into the viewer, updates the counter, and lets users close the viewer with a button or the Escape key. The image visuals are created entirely with CSS.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-sixteen]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-sixteen-grid]");
  const viewer = gallery.querySelector("[data-vb-gallery-sixteen-viewer]");
  const closeButton = gallery.querySelector("[data-vb-gallery-sixteen-close]");
  const stageArt = gallery.querySelector("[data-vb-gallery-sixteen-stage-art]");
  const viewerTitle = gallery.querySelector("[data-vb-gallery-sixteen-viewer-title]");
  const viewerCategory = gallery.querySelector("[data-vb-gallery-sixteen-viewer-category]");
  const viewerDescription = gallery.querySelector("[data-vb-gallery-sixteen-viewer-description]");
  const counter = gallery.querySelector("[data-vb-gallery-sixteen-counter]");

  const items = [
    {
      title: "Museum Screen",
      category: "Portfolio",
      art: "vb-gallery-sixteen-art-museum",
      stageArt: "vb-gallery-sixteen-stage-art-museum",
      description: "A fullscreen CSS visual for portfolio previews and creative case studies."
    },
    {
      title: "Open Sky",
      category: "Landscape",
      art: "vb-gallery-sixteen-art-sky",
      stageArt: "vb-gallery-sixteen-stage-art-sky",
      description: "A fullscreen CSS landscape viewer for focused gallery browsing."
    },
    {
      title: "Product Object",
      category: "Product",
      art: "vb-gallery-sixteen-art-object",
      stageArt: "vb-gallery-sixteen-stage-art-object",
      description: "A CSS product-style visual opened inside a fullscreen JavaScript viewer."
    },
    {
      title: "Green Field",
      category: "Nature",
      art: "vb-gallery-sixteen-art-green",
      stageArt: "vb-gallery-sixteen-stage-art-green",
      description: "A CSS nature-style illustration for immersive gallery viewing."
    }
  ];

  function createCard(item, index) {
    return `
      <button type="button" class="vb-gallery-sixteen-card" data-index="${index}">
        <div class="vb-gallery-sixteen-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-sixteen-body">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
        </div>
      </button>
    `;
  }

  function openViewer(index) {
    const item = items[index];

    stageArt.className = "vb-gallery-sixteen-stage-art " + item.stageArt;
    viewerTitle.textContent = item.title;
    viewerCategory.textContent = item.category;
    viewerDescription.textContent = item.description;
    counter.textContent = Number(index) + 1 + " / " + items.length;

    viewer.hidden = false;
    document.body.style.overflow = "hidden";
  }

  function closeViewer() {
    viewer.hidden = true;
    document.body.style.overflow = "";
  }

  grid.addEventListener("click", function (event) {
    const card = event.target.closest("[data-index]");
    if (!card) return;

    openViewer(card.getAttribute("data-index"));
  });

  closeButton.addEventListener("click", closeViewer);

  document.addEventListener("keydown", function (event) {
    if (event.key === "Escape" && !viewer.hidden) {
      closeViewer();
    }
  });

  grid.innerHTML = items.map(createCard).join("");
})();

HTML

<div class="vb-gallery-sixteen-demo">
  <div class="vb-gallery-sixteen-shell" data-vb-gallery-sixteen>
    <div class="vb-gallery-sixteen-sidebar">
      <span>Example 16</span>
      <h3>Fullscreen Image Viewer Gallery</h3>
      <p>Open CSS-illustrated gallery cards in a focused fullscreen-style JavaScript viewer.</p>
    </div>

    <div class="vb-gallery-sixteen-grid" data-vb-gallery-sixteen-grid></div>

    <div class="vb-gallery-sixteen-viewer" data-vb-gallery-sixteen-viewer hidden>
      <div class="vb-gallery-sixteen-topbar">
        <span data-vb-gallery-sixteen-counter>1 / 1</span>
        <strong data-vb-gallery-sixteen-viewer-category>Category</strong>
        <button type="button" data-vb-gallery-sixteen-close>Close</button>
      </div>

      <div class="vb-gallery-sixteen-stage">
        <div class="vb-gallery-sixteen-stage-art" data-vb-gallery-sixteen-stage-art></div>
      </div>

      <div class="vb-gallery-sixteen-caption">
        <h4 data-vb-gallery-sixteen-viewer-title></h4>
        <p data-vb-gallery-sixteen-viewer-description></p>
      </div>
    </div>
  </div>
</div>

CSS

.vb-gallery-sixteen-demo,
.vb-gallery-sixteen-demo * {
  box-sizing: border-box;
}

.vb-gallery-sixteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 28px;
  background:
    linear-gradient(90deg, rgba(15,23,42,0.05) 1px, transparent 1px),
    linear-gradient(0deg, rgba(15,23,42,0.05) 1px, transparent 1px),
    linear-gradient(135deg, #f8fafc 0%, #f1f5f9 52%, #ffffff 100%) !important;
  background-size: 32px 32px, 32px 32px, auto !important;
  border: 1px solid rgba(100, 116, 139, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-gallery-sixteen-shell {
  display: grid;
  grid-template-columns: minmax(260px, 360px) minmax(0, 1fr);
  gap: 18px;
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-sixteen-sidebar {
  display: grid;
  align-content: center;
  min-width: 0;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 28px;
  background:
    radial-gradient(circle at 16% 18%, rgba(255,255,255,0.14), transparent 34%),
    linear-gradient(135deg, #020617, #111827 58%, #334155) !important;
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.25);
}

.vb-gallery-sixteen-sidebar span {
  display: inline-flex;
  width: fit-content;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-sixteen-sidebar h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 4.7vw, 58px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-sixteen-sidebar p {
  margin: 0 !important;
  color: #e2e8f0 !important;
  -webkit-text-fill-color: #e2e8f0 !important;
  font-size: 15px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-sixteen-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-sixteen-card {
  overflow: hidden;
  padding: 0;
  border: 0;
  border-radius: 28px;
  background: #ffffff;
  cursor: pointer;
  text-align: left;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  transition: transform 0.24s ease, box-shadow 0.24s ease;
}

.vb-gallery-sixteen-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 26px 64px rgba(15, 23, 42, 0.16);
}

.vb-gallery-sixteen-art {
  position: relative;
  height: 260px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-sixteen-art::before,
.vb-gallery-sixteen-art::after,
.vb-gallery-sixteen-stage-art::before,
.vb-gallery-sixteen-stage-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-sixteen-art-museum,
.vb-gallery-sixteen-stage-art-museum {
  background: linear-gradient(135deg, #020617 0%, #312e81 52%, #7c3aed 100%);
}

.vb-gallery-sixteen-art-museum::before,
.vb-gallery-sixteen-stage-art-museum::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 58%;
  border-radius: 30px;
  background:
    linear-gradient(135deg, rgba(255,255,255,0.22), rgba(255,255,255,0.04)),
    repeating-linear-gradient(90deg, rgba(255,255,255,0.16) 0 4px, transparent 4px 24px);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-sixteen-art-museum::after,
.vb-gallery-sixteen-stage-art-museum::after {
  right: 16%;
  bottom: 18%;
  width: 92px;
  height: 92px;
  border-radius: 999px;
  background: rgba(255,255,255,0.24);
  filter: blur(8px);
}

.vb-gallery-sixteen-art-sky,
.vb-gallery-sixteen-stage-art-sky {
  background: linear-gradient(180deg, #dbeafe 0%, #3b82f6 50%, #1e3a8a 100%);
}

.vb-gallery-sixteen-art-sky::before,
.vb-gallery-sixteen-stage-art-sky::before {
  left: -8%;
  right: -8%;
  bottom: -20%;
  height: 56%;
  border-radius: 50% 50% 0 0;
  background: #172554;
}

.vb-gallery-sixteen-art-sky::after,
.vb-gallery-sixteen-stage-art-sky::after {
  right: 18%;
  top: 16%;
  width: 88px;
  height: 88px;
  border-radius: 999px;
  background: rgba(255,255,255,0.86);
  box-shadow: 0 0 64px rgba(255,255,255,0.70);
}

.vb-gallery-sixteen-art-object,
.vb-gallery-sixteen-stage-art-object {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #f3e8ff 36%, #581c87 100%);
}

.vb-gallery-sixteen-art-object::before,
.vb-gallery-sixteen-stage-art-object::before {
  left: 50%;
  top: 50%;
  width: 142px;
  height: 142px;
  border-radius: 34px;
  background: linear-gradient(135deg, #ffffff, #ddd6fe);
  box-shadow: 0 30px 80px rgba(88,28,135,0.34);
  transform: translate(-50%, -50%) rotate(16deg);
}

.vb-gallery-sixteen-art-object::after,
.vb-gallery-sixteen-stage-art-object::after {
  left: 50%;
  top: 50%;
  width: 74px;
  height: 74px;
  border-radius: 22px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(16deg);
}

.vb-gallery-sixteen-art-green,
.vb-gallery-sixteen-stage-art-green {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-sixteen-art-green::before,
.vb-gallery-sixteen-stage-art-green::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-sixteen-art-green::after,
.vb-gallery-sixteen-stage-art-green::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-sixteen-body {
  display: grid;
  gap: 8px;
  padding: 18px;
}

.vb-gallery-sixteen-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #f1f5f9;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-sixteen-body h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 22px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-sixteen-viewer {
  position: fixed;
  inset: 0;
  z-index: 99999;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background: #020617;
}

.vb-gallery-sixteen-viewer[hidden] {
  display: none;
}

.vb-gallery-sixteen-topbar {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 14px;
  padding: 16px clamp(16px, 4vw, 34px);
  background: rgba(15, 23, 42, 0.92);
  border-bottom: 1px solid rgba(255,255,255,0.10);
}

.vb-gallery-sixteen-topbar span,
.vb-gallery-sixteen-topbar strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-gallery-sixteen-topbar strong {
  text-align: center;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
}

.vb-gallery-sixteen-topbar button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: #020617;
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-gallery-sixteen-stage {
  display: grid;
  place-items: center;
  padding: clamp(16px, 4vw, 34px);
}

.vb-gallery-sixteen-stage-art {
  position: relative;
  width: min(980px, 100%);
  height: min(60vh, 620px);
  min-height: 360px;
  overflow: hidden;
  border-radius: 34px;
  box-shadow: 0 34px 110px rgba(0,0,0,0.45);
}

.vb-gallery-sixteen-caption {
  padding: 20px clamp(16px, 4vw, 34px) 28px;
  background: rgba(15, 23, 42, 0.92);
  border-top: 1px solid rgba(255,255,255,0.10);
}

.vb-gallery-sixteen-caption h4 {
  max-width: 920px;
  margin: 0 auto 8px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 4vw, 52px) !important;
  line-height: 1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.065em;
}

.vb-gallery-sixteen-caption p {
  max-width: 920px;
  margin: 0 auto !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

@media (max-width: 940px) {
  .vb-gallery-sixteen-shell {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 660px) {
  .vb-gallery-sixteen-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-sixteen-sidebar h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-sixteen-grid {
    grid-template-columns: 1fr;
  }

  .vb-gallery-sixteen-topbar {
    grid-template-columns: 1fr;
    text-align: center;
  }

  .vb-gallery-sixteen-stage-art {
    min-height: 300px;
    border-radius: 24px;
  }
}

This fullscreen image viewer gallery is useful for portfolio previews, product showcases, design galleries, creative case studies, photography-style sections, and visual landing pages where one selected image should become the main focus.

17. Image Gallery with Drag and Drop Reordering

An image gallery with drag and drop reordering lets users rearrange visual items directly in the interface. This pattern is useful for portfolio builders, media libraries, product image managers, project dashboards, website builders, moodboards, and admin panels where image order matters.

This example uses JavaScript drag and drop events. Users can drag a CSS-illustrated card to a new position, the array order updates, and the gallery re-renders with the new order. All image thumbnails are made with CSS only, so the demo does not load external images.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-seventeen]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-seventeen-grid]");
  const status = gallery.querySelector("[data-vb-gallery-seventeen-status]");

  let draggedId = null;

  let items = [
    {
      id: "blue-panel",
      title: "Blue Panel",
      category: "Portfolio",
      art: "vb-gallery-seventeen-art-blue",
      description: "Drag this CSS gallery card to change its position."
    },
    {
      id: "orange-peaks",
      title: "Orange Peaks",
      category: "Travel",
      art: "vb-gallery-seventeen-art-orange",
      description: "The gallery order updates after dropping this card."
    },
    {
      id: "green-route",
      title: "Green Route",
      category: "Nature",
      art: "vb-gallery-seventeen-art-green",
      description: "A CSS-only image card inside a reorderable gallery."
    },
    {
      id: "purple-object",
      title: "Purple Object",
      category: "Product",
      art: "vb-gallery-seventeen-art-purple",
      description: "Use drag and drop to move this product-style card."
    },
    {
      id: "dark-lines",
      title: "Dark Lines",
      category: "Editorial",
      art: "vb-gallery-seventeen-art-dark",
      description: "A dark CSS visual card for drag sorting behavior."
    },
    {
      id: "pink-shape",
      title: "Pink Shape",
      category: "Abstract",
      art: "vb-gallery-seventeen-art-pink",
      description: "A creative CSS thumbnail that can be reordered."
    }
  ];

  function createCard(item, index) {
    return `
      <article class="vb-gallery-seventeen-card" draggable="true" data-id="${item.id}" style="animation-delay: ${index * 40}ms">
        <div class="vb-gallery-seventeen-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-seventeen-body">
          <small>Order ${index + 1} · ${item.category}</small>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function renderGallery() {
    grid.innerHTML = items.map(createCard).join("");
  }

  function moveItem(dragId, targetId) {
    if (dragId === targetId) return;

    const dragIndex = items.findIndex(function (item) {
      return item.id === dragId;
    });

    const targetIndex = items.findIndex(function (item) {
      return item.id === targetId;
    });

    if (dragIndex < 0 || targetIndex < 0) return;

    const draggedItem = items.splice(dragIndex, 1)[0];
    items.splice(targetIndex, 0, draggedItem);

    status.textContent = "New order saved in JavaScript state";
    renderGallery();
  }

  grid.addEventListener("dragstart", function (event) {
    const card = event.target.closest("[data-id]");
    if (!card) return;

    draggedId = card.getAttribute("data-id");
    card.classList.add("is-dragging");
    event.dataTransfer.effectAllowed = "move";
  });

  grid.addEventListener("dragover", function (event) {
    event.preventDefault();

    const card = event.target.closest("[data-id]");
    if (!card || card.getAttribute("data-id") === draggedId) return;

    grid.querySelectorAll(".vb-gallery-seventeen-card").forEach(function (item) {
      item.classList.remove("is-drag-over");
    });

    card.classList.add("is-drag-over");
  });

  grid.addEventListener("dragleave", function (event) {
    const card = event.target.closest("[data-id]");
    if (card) {
      card.classList.remove("is-drag-over");
    }
  });

  grid.addEventListener("drop", function (event) {
    event.preventDefault();

    const card = event.target.closest("[data-id]");
    if (!card || !draggedId) return;

    moveItem(draggedId, card.getAttribute("data-id"));
    draggedId = null;
  });

  grid.addEventListener("dragend", function () {
    draggedId = null;

    grid.querySelectorAll(".vb-gallery-seventeen-card").forEach(function (card) {
      card.classList.remove("is-dragging", "is-drag-over");
    });
  });

  renderGallery();
})();

HTML

<div class="vb-gallery-seventeen-demo">
  <div class="vb-gallery-seventeen-shell" data-vb-gallery-seventeen>
    <div class="vb-gallery-seventeen-head">
      <div>
        <span>Example 17</span>
        <h3>Image Gallery with Drag and Drop Reordering</h3>
        <p>Drag CSS-illustrated gallery cards into a new order and update the layout with JavaScript.</p>
      </div>

      <strong data-vb-gallery-seventeen-status>Drag a card to reorder</strong>
    </div>

    <div class="vb-gallery-seventeen-grid" data-vb-gallery-seventeen-grid></div>
  </div>
</div>

CSS

.vb-gallery-seventeen-demo,
.vb-gallery-seventeen-demo * {
  box-sizing: border-box;
}

.vb-gallery-seventeen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 14% 14%, rgba(14, 165, 233, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(168, 85, 247, 0.16), transparent 34%),
    linear-gradient(135deg, #f0f9ff 0%, #faf5ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.22);
  box-shadow: 0 28px 80px rgba(12, 74, 110, 0.10);
}

.vb-gallery-seventeen-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-seventeen-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 24px;
}

.vb-gallery-seventeen-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-seventeen-head h3 {
  max-width: 820px;
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-seventeen-head p {
  max-width: 720px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-seventeen-head strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-seventeen-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-gallery-seventeen-card {
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 2px solid transparent;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  cursor: grab;
  transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease, opacity 0.22s ease;
  animation: vbGallerySeventeenIn 0.32s ease both;
}

@keyframes vbGallerySeventeenIn {
  from {
    opacity: 0;
    transform: translateY(14px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-seventeen-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 26px 64px rgba(15, 23, 42, 0.16);
}

.vb-gallery-seventeen-card.is-dragging {
  opacity: 0.46;
  transform: scale(0.96);
  cursor: grabbing;
}

.vb-gallery-seventeen-card.is-drag-over {
  border-color: rgba(14, 165, 233, 0.88);
  box-shadow: 0 24px 70px rgba(14, 165, 233, 0.22);
}

.vb-gallery-seventeen-art {
  position: relative;
  height: 240px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-seventeen-art::before,
.vb-gallery-seventeen-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-seventeen-art-blue {
  background: linear-gradient(135deg, #020617 0%, #2563eb 54%, #38bdf8 100%);
}

.vb-gallery-seventeen-art-blue::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 60%;
  border-radius: 28px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 20px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-seventeen-art-blue::after {
  right: 16%;
  bottom: 18%;
  width: 92px;
  height: 92px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-seventeen-art-orange {
  background: linear-gradient(180deg, #fed7aa 0%, #f97316 50%, #7c2d12 100%);
}

.vb-gallery-seventeen-art-orange::before {
  left: -8%;
  right: -8%;
  bottom: -20%;
  height: 58%;
  background: #431407;
  clip-path: polygon(0 100%, 0 58%, 16% 42%, 32% 68%, 50% 36%, 68% 62%, 84% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-seventeen-art-orange::after {
  right: 18%;
  top: 16%;
  width: 78px;
  height: 78px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 62px rgba(255,247,237,0.72);
}

.vb-gallery-seventeen-art-green {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-seventeen-art-green::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-seventeen-art-green::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-seventeen-art-purple {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #ddd6fe 36%, #581c87 100%);
}

.vb-gallery-seventeen-art-purple::before {
  left: 50%;
  top: 50%;
  width: 136px;
  height: 136px;
  border-radius: 34px;
  background: linear-gradient(135deg, #ffffff, #c4b5fd);
  box-shadow: 0 30px 80px rgba(88,28,135,0.34);
  transform: translate(-50%, -50%) rotate(16deg);
}

.vb-gallery-seventeen-art-purple::after {
  left: 50%;
  top: 50%;
  width: 74px;
  height: 74px;
  border-radius: 22px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(16deg);
}

.vb-gallery-seventeen-art-dark {
  background: linear-gradient(180deg, #020617 0%, #1e293b 58%, #0f172a 100%);
}

.vb-gallery-seventeen-art-dark::before {
  left: 14%;
  right: 14%;
  top: 18%;
  height: 4px;
  background: repeating-linear-gradient(90deg, #ffffff 0 12px, transparent 12px 28px);
  box-shadow:
    0 44px 0 rgba(255,255,255,0.56),
    0 88px 0 rgba(255,255,255,0.30),
    0 132px 0 rgba(255,255,255,0.18);
}

.vb-gallery-seventeen-art-dark::after {
  right: 15%;
  top: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #facc15;
  box-shadow: 0 0 70px rgba(250,204,21,0.60);
}

.vb-gallery-seventeen-art-pink {
  background: linear-gradient(135deg, #500724 0%, #be123c 52%, #fb7185 100%);
}

.vb-gallery-seventeen-art-pink::before {
  left: 50%;
  top: 52%;
  width: 150px;
  height: 150px;
  border-radius: 42% 58% 62% 38%;
  background: radial-gradient(circle at 35% 30%, #fff1f2, #fb7185 44%, #9f1239 100%);
  transform: translate(-50%, -50%) rotate(22deg);
  box-shadow: 0 26px 70px rgba(80,7,36,0.36);
}

.vb-gallery-seventeen-art-pink::after {
  left: 14%;
  top: 16%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: rgba(255,255,255,0.22);
  filter: blur(6px);
}

.vb-gallery-seventeen-body {
  display: grid;
  gap: 8px;
  padding: 18px;
}

.vb-gallery-seventeen-body small {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #f0f9ff;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-seventeen-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 22px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-seventeen-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

@media (max-width: 980px) {
  .vb-gallery-seventeen-head {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-seventeen-head strong {
    justify-content: center;
    width: 100%;
  }

  .vb-gallery-seventeen-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .vb-gallery-seventeen-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-seventeen-head h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-seventeen-grid {
    grid-template-columns: 1fr;
  }
}

This drag and drop image gallery is useful for portfolio builders, media management dashboards, product image ordering, website builders, image approval tools, moodboards, and visual admin interfaces where users need to control the display order.

18. Image Gallery with Compare Mode

An image gallery with compare mode lets users select two images and view them side by side. This is useful for product comparison, design review, before-and-after layouts, portfolio selection, real estate shortlist pages, visual approval tools, and media library interfaces.

This example uses JavaScript compare selection logic. Users can select up to two CSS-illustrated gallery items, the compare panel updates automatically, and selecting a third item replaces the oldest selected item. No external images are loaded.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-eighteen]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-eighteen-grid]");
  const slots = gallery.querySelector("[data-vb-gallery-eighteen-slots]");
  const status = gallery.querySelector("[data-vb-gallery-eighteen-status]");
  const clearButton = gallery.querySelector("[data-vb-gallery-eighteen-clear]");

  const items = [
    {
      id: "compare-blue",
      title: "Blue Interface",
      category: "Portfolio",
      art: "vb-gallery-eighteen-art-a",
      slotArt: "vb-gallery-eighteen-slot-art-a"
    },
    {
      id: "compare-sunset",
      title: "Sunset Layout",
      category: "Travel",
      art: "vb-gallery-eighteen-art-b",
      slotArt: "vb-gallery-eighteen-slot-art-b"
    },
    {
      id: "compare-object",
      title: "Product Object",
      category: "Product",
      art: "vb-gallery-eighteen-art-c",
      slotArt: "vb-gallery-eighteen-slot-art-c"
    },
    {
      id: "compare-forest",
      title: "Forest Scene",
      category: "Nature",
      art: "vb-gallery-eighteen-art-d",
      slotArt: "vb-gallery-eighteen-slot-art-d"
    }
  ];

  let selectedIds = [];

  function isSelected(id) {
    return selectedIds.includes(id);
  }

  function createCard(item) {
    const selectedClass = isSelected(item.id) ? "is-selected" : "";

    return `
      <article class="vb-gallery-eighteen-card ${selectedClass}" data-id="${item.id}">
        <div class="vb-gallery-eighteen-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-eighteen-body">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
        </div>
      </article>
    `;
  }

  function createSlot(item) {
    return `
      <div class="vb-gallery-eighteen-slot">
        <div class="vb-gallery-eighteen-slot-art ${item.slotArt}" aria-label="${item.title} compare illustration" role="img"></div>

        <div class="vb-gallery-eighteen-slot-body">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
        </div>
      </div>
    `;
  }

  function createPlaceholder(number) {
    return `
      <div class="vb-gallery-eighteen-placeholder">
        <div>
          <span>Slot ${number}</span>
          <p>Select ${number === 1 ? "first" : "second"} image</p>
        </div>
      </div>
    `;
  }

  function updateStatus() {
    if (selectedIds.length === 0) {
      status.textContent = "Choose 2 images";
    }

    if (selectedIds.length === 1) {
      status.textContent = "Choose 1 more image";
    }

    if (selectedIds.length === 2) {
      status.textContent = "Compare mode active";
    }
  }

  function renderSlots() {
    const selectedItems = selectedIds.map(function (id) {
      return items.find(function (item) {
        return item.id === id;
      });
    }).filter(Boolean);

    let markup = selectedItems.map(createSlot).join("");

    while (selectedItems.length < 2) {
      markup += createPlaceholder(selectedItems.length + 1);
      selectedItems.push(null);
    }

    slots.innerHTML = markup;
  }

  function renderGallery() {
    grid.innerHTML = items.map(createCard).join("");
    renderSlots();
    updateStatus();
  }

  function toggleCompare(id) {
    if (isSelected(id)) {
      selectedIds = selectedIds.filter(function (selectedId) {
        return selectedId !== id;
      });
    } else {
      if (selectedIds.length >= 2) {
        selectedIds.shift();
      }

      selectedIds.push(id);
    }

    renderGallery();
  }

  grid.addEventListener("click", function (event) {
    const card = event.target.closest("[data-id]");
    if (!card) return;

    toggleCompare(card.getAttribute("data-id"));
  });

  clearButton.addEventListener("click", function () {
    selectedIds = [];
    renderGallery();
  });

  renderGallery();
})();

HTML

<div class="vb-gallery-eighteen-demo">
  <div class="vb-gallery-eighteen-shell" data-vb-gallery-eighteen>
    <div class="vb-gallery-eighteen-intro">
      <span>Example 18</span>
      <h3>Image Gallery with Compare Mode</h3>
      <p>Select two CSS-illustrated gallery items and compare them side by side with JavaScript state.</p>
    </div>

    <div class="vb-gallery-eighteen-layout">
      <div class="vb-gallery-eighteen-grid" data-vb-gallery-eighteen-grid></div>

      <aside class="vb-gallery-eighteen-compare">
        <div class="vb-gallery-eighteen-compare-head">
          <strong data-vb-gallery-eighteen-status>Choose 2 images</strong>
          <button type="button" data-vb-gallery-eighteen-clear>Clear</button>
        </div>

        <div class="vb-gallery-eighteen-slots" data-vb-gallery-eighteen-slots>
          <div class="vb-gallery-eighteen-placeholder">
            <span>Slot 1</span>
            <p>Select first image</p>
          </div>

          <div class="vb-gallery-eighteen-placeholder">
            <span>Slot 2</span>
            <p>Select second image</p>
          </div>
        </div>
      </aside>
    </div>
  </div>
</div>

CSS

.vb-gallery-eighteen-demo,
.vb-gallery-eighteen-demo * {
  box-sizing: border-box;
}

.vb-gallery-eighteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 18px 46px 18px 46px;
  background:
    radial-gradient(circle at 14% 14%, rgba(16, 185, 129, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(59, 130, 246, 0.16), transparent 34%),
    linear-gradient(135deg, #ecfdf5 0%, #eff6ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(16, 185, 129, 0.22);
  box-shadow: 0 28px 80px rgba(6, 95, 70, 0.10);
}

.vb-gallery-eighteen-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-eighteen-intro {
  margin-bottom: 22px;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 10px 34px 10px 34px;
  background:
    radial-gradient(circle at 16% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #064e3b, #1e3a8a 58%, #312e81) !important;
  box-shadow: 0 24px 70px rgba(6, 95, 70, 0.22);
}

.vb-gallery-eighteen-intro span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-eighteen-intro h3 {
  max-width: 820px;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-eighteen-intro p {
  max-width: 740px;
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-eighteen-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 370px);
  gap: 18px;
  align-items: start;
}

.vb-gallery-eighteen-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-eighteen-card {
  overflow: hidden;
  border: 2px solid transparent;
  border-radius: 28px;
  background: #ffffff;
  cursor: pointer;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease;
}

.vb-gallery-eighteen-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 26px 64px rgba(15, 23, 42, 0.15);
}

.vb-gallery-eighteen-card.is-selected {
  border-color: rgba(16, 185, 129, 0.90);
  box-shadow: 0 24px 70px rgba(16, 185, 129, 0.22);
}

.vb-gallery-eighteen-art {
  position: relative;
  height: 225px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-eighteen-art::before,
.vb-gallery-eighteen-art::after,
.vb-gallery-eighteen-slot-art::before,
.vb-gallery-eighteen-slot-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-eighteen-art-a,
.vb-gallery-eighteen-slot-art-a {
  background: linear-gradient(135deg, #020617 0%, #2563eb 54%, #38bdf8 100%);
}

.vb-gallery-eighteen-art-a::before,
.vb-gallery-eighteen-slot-art-a::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 60%;
  border-radius: 28px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 20px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-eighteen-art-a::after,
.vb-gallery-eighteen-slot-art-a::after {
  right: 16%;
  bottom: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-eighteen-art-b,
.vb-gallery-eighteen-slot-art-b {
  background: linear-gradient(180deg, #fed7aa 0%, #f97316 50%, #7c2d12 100%);
}

.vb-gallery-eighteen-art-b::before,
.vb-gallery-eighteen-slot-art-b::before {
  left: -8%;
  right: -8%;
  bottom: -20%;
  height: 58%;
  background: #431407;
  clip-path: polygon(0 100%, 0 58%, 16% 42%, 32% 68%, 50% 36%, 68% 62%, 84% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-eighteen-art-b::after,
.vb-gallery-eighteen-slot-art-b::after {
  right: 18%;
  top: 16%;
  width: 72px;
  height: 72px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 62px rgba(255,247,237,0.72);
}

.vb-gallery-eighteen-art-c,
.vb-gallery-eighteen-slot-art-c {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #ddd6fe 36%, #581c87 100%);
}

.vb-gallery-eighteen-art-c::before,
.vb-gallery-eighteen-slot-art-c::before {
  left: 50%;
  top: 50%;
  width: 124px;
  height: 124px;
  border-radius: 34px;
  background: linear-gradient(135deg, #ffffff, #c4b5fd);
  box-shadow: 0 30px 80px rgba(88,28,135,0.34);
  transform: translate(-50%, -50%) rotate(16deg);
}

.vb-gallery-eighteen-art-c::after,
.vb-gallery-eighteen-slot-art-c::after {
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  border-radius: 22px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(16deg);
}

.vb-gallery-eighteen-art-d,
.vb-gallery-eighteen-slot-art-d {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-eighteen-art-d::before,
.vb-gallery-eighteen-slot-art-d::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-eighteen-art-d::after,
.vb-gallery-eighteen-slot-art-d::after {
  right: 16%;
  top: 16%;
  width: 76px;
  height: 76px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-eighteen-body {
  display: grid;
  gap: 8px;
  padding: 17px;
}

.vb-gallery-eighteen-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #ecfdf5;
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-eighteen-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-eighteen-compare {
  position: sticky;
  top: 18px;
  padding: 16px;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
}

.vb-gallery-eighteen-compare-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.vb-gallery-eighteen-compare-head strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 950;
}

.vb-gallery-eighteen-compare-head button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 9px 12px;
  font-size: 12px;
  font-weight: 950;
  cursor: pointer;
}

.vb-gallery-eighteen-slots {
  display: grid;
  gap: 12px;
}

.vb-gallery-eighteen-slot,
.vb-gallery-eighteen-placeholder {
  overflow: hidden;
  border-radius: 22px;
  border: 1px solid rgba(148, 163, 184, 0.24);
  background: #f8fafc;
}

.vb-gallery-eighteen-placeholder {
  display: grid;
  place-items: center;
  min-height: 170px;
  padding: 20px;
  text-align: center;
  border-style: dashed;
}

.vb-gallery-eighteen-placeholder span {
  display: inline-flex;
  margin-bottom: 8px;
  padding: 6px 9px;
  border-radius: 999px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-eighteen-placeholder p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-gallery-eighteen-slot-art {
  position: relative;
  height: 150px;
  overflow: hidden;
}

.vb-gallery-eighteen-slot-body {
  padding: 13px;
}

.vb-gallery-eighteen-slot-body span {
  display: inline-flex;
  margin-bottom: 7px;
  padding: 5px 8px;
  border-radius: 999px;
  background: #ecfdf5;
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
  font-size: 10px;
  font-weight: 950;
}

.vb-gallery-eighteen-slot-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.04em;
}

@media (max-width: 980px) {
  .vb-gallery-eighteen-layout {
    grid-template-columns: 1fr;
  }

  .vb-gallery-eighteen-compare {
    position: static;
  }

  .vb-gallery-eighteen-slots {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 620px) {
  .vb-gallery-eighteen-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-eighteen-intro h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-eighteen-grid,
  .vb-gallery-eighteen-slots {
    grid-template-columns: 1fr;
  }
}

This image gallery with compare mode is useful for product comparisons, design reviews, before-and-after interfaces, real estate shortlists, portfolio selection tools, media libraries, and image approval dashboards.

19. Image Gallery with Slideshow Mode

An image gallery with slideshow mode lets visitors watch gallery items automatically instead of clicking through every card manually. This is useful for homepage galleries, product highlights, portfolio showcases, event recaps, presentation sections, travel galleries, and visual landing pages.

This example uses JavaScript slideshow state. Users can start or pause the slideshow, move to the next or previous item manually, and watch the progress bar reset for each slide. The gallery thumbnails and large preview are CSS-only illustrations, so no external image files are loaded.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-nineteen]");
  if (!gallery) return;

  const stageArt = gallery.querySelector("[data-vb-gallery-nineteen-art]");
  const title = gallery.querySelector("[data-vb-gallery-nineteen-title]");
  const category = gallery.querySelector("[data-vb-gallery-nineteen-category]");
  const description = gallery.querySelector("[data-vb-gallery-nineteen-description]");
  const status = gallery.querySelector("[data-vb-gallery-nineteen-status]");
  const progress = gallery.querySelector("[data-vb-gallery-nineteen-progress]");
  const prevButton = gallery.querySelector("[data-vb-gallery-nineteen-prev]");
  const nextButton = gallery.querySelector("[data-vb-gallery-nineteen-next]");
  const toggleButton = gallery.querySelector("[data-vb-gallery-nineteen-toggle]");
  const thumbs = gallery.querySelector("[data-vb-gallery-nineteen-thumbs]");

  const items = [
    {
      title: "Cinema Portfolio",
      category: "Portfolio",
      art: "vb-gallery-nineteen-art-cinema",
      thumbArt: "vb-gallery-nineteen-thumb-art-cinema",
      description: "A CSS-only portfolio slide for homepage galleries and creative showcases."
    },
    {
      title: "Sunset Campaign",
      category: "Travel",
      art: "vb-gallery-nineteen-art-sunset",
      thumbArt: "vb-gallery-nineteen-thumb-art-sunset",
      description: "A warm travel-inspired CSS slide for automatic visual storytelling."
    },
    {
      title: "Product Feature",
      category: "Product",
      art: "vb-gallery-nineteen-art-product",
      thumbArt: "vb-gallery-nineteen-thumb-art-product",
      description: "A CSS product-style slide for launch sections and visual product galleries."
    },
    {
      title: "Forest Story",
      category: "Nature",
      art: "vb-gallery-nineteen-art-forest",
      thumbArt: "vb-gallery-nineteen-thumb-art-forest",
      description: "A CSS nature slide for peaceful galleries, landing pages, and visual recaps."
    }
  ];

  let currentIndex = 0;
  let isPlaying = false;
  let timerId = null;
  const slideDuration = 3200;

  function createThumb(item, index) {
    const activeClass = index === currentIndex ? "is-active" : "";

    return `
      <button type="button" class="vb-gallery-nineteen-thumb ${activeClass}" data-index="${index}">
        <div class="vb-gallery-nineteen-thumb-art ${item.thumbArt}" aria-label="${item.title} thumbnail" role="img"></div>
        <strong>${item.title}</strong>
      </button>
    `;
  }

  function renderThumbs() {
    thumbs.innerHTML = items.map(createThumb).join("");
  }

  function resetProgress() {
    progress.classList.remove("is-playing");
    progress.style.animation = "none";
    progress.offsetHeight;
    progress.style.animation = "";
    progress.classList.toggle("is-playing", isPlaying);
  }

  function renderSlide() {
    const item = items[currentIndex];

    stageArt.classList.add("is-changing");

    window.setTimeout(function () {
      stageArt.className = "vb-gallery-nineteen-art " + item.art;
      title.textContent = item.title;
      category.textContent = item.category;
      description.textContent = item.description;
      renderThumbs();
      resetProgress();
    }, 160);
  }

  function showNext() {
    currentIndex = (currentIndex + 1) % items.length;
    renderSlide();
  }

  function showPrevious() {
    currentIndex = (currentIndex - 1 + items.length) % items.length;
    renderSlide();
  }

  function stopTimer() {
    window.clearInterval(timerId);
    timerId = null;
  }

  function startTimer() {
    stopTimer();
    timerId = window.setInterval(showNext, slideDuration);
  }

  function updatePlayState() {
    status.textContent = isPlaying ? "Playing slideshow" : "Paused";
    toggleButton.textContent = isPlaying ? "Pause Slideshow" : "Start Slideshow";
    resetProgress();

    if (isPlaying) {
      startTimer();
    } else {
      stopTimer();
    }
  }

  prevButton.addEventListener("click", function () {
    showPrevious();

    if (isPlaying) {
      startTimer();
    }
  });

  nextButton.addEventListener("click", function () {
    showNext();

    if (isPlaying) {
      startTimer();
    }
  });

  toggleButton.addEventListener("click", function () {
    isPlaying = !isPlaying;
    updatePlayState();
  });

  thumbs.addEventListener("click", function (event) {
    const thumb = event.target.closest("[data-index]");
    if (!thumb) return;

    currentIndex = Number(thumb.getAttribute("data-index"));
    renderSlide();

    if (isPlaying) {
      startTimer();
    }
  });

  renderSlide();
  updatePlayState();
})();

HTML

<div class="vb-gallery-nineteen-demo">
  <div class="vb-gallery-nineteen-shell" data-vb-gallery-nineteen>
    <div class="vb-gallery-nineteen-header">
      <div>
        <span>Example 19</span>
        <h3>Image Gallery with Slideshow Mode</h3>
        <p>Turn a CSS-illustrated gallery into an automatic slideshow with JavaScript controls.</p>
      </div>

      <strong data-vb-gallery-nineteen-status>Paused</strong>
    </div>

    <div class="vb-gallery-nineteen-stage">
      <div class="vb-gallery-nineteen-art" data-vb-gallery-nineteen-art></div>

      <div class="vb-gallery-nineteen-overlay">
        <span data-vb-gallery-nineteen-category>Category</span>
        <h4 data-vb-gallery-nineteen-title>Gallery Title</h4>
        <p data-vb-gallery-nineteen-description>Gallery description.</p>

        <div class="vb-gallery-nineteen-progress">
          <div data-vb-gallery-nineteen-progress></div>
        </div>
      </div>
    </div>

    <div class="vb-gallery-nineteen-controls">
      <button type="button" data-vb-gallery-nineteen-prev>Previous</button>
      <button type="button" data-vb-gallery-nineteen-toggle>Start Slideshow</button>
      <button type="button" data-vb-gallery-nineteen-next>Next</button>
    </div>

    <div class="vb-gallery-nineteen-thumbs" data-vb-gallery-nineteen-thumbs></div>
  </div>
</div>

CSS

.vb-gallery-nineteen-demo,
.vb-gallery-nineteen-demo * {
  box-sizing: border-box;
}

.vb-gallery-nineteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 42px;
  background:
    radial-gradient(circle at 12% 12%, rgba(79, 70, 229, 0.18), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(236, 72, 153, 0.16), transparent 34%),
    linear-gradient(135deg, #eef2ff 0%, #fdf2f8 52%, #ffffff 100%) !important;
  border: 1px solid rgba(129, 140, 248, 0.24);
  box-shadow: 0 28px 80px rgba(67, 56, 202, 0.10);
}

.vb-gallery-nineteen-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-nineteen-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 22px;
}

.vb-gallery-nineteen-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #e0e7ff;
  color: #3730a3 !important;
  -webkit-text-fill-color: #3730a3 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-nineteen-header h3 {
  max-width: 820px;
  margin: 0 0 14px !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-nineteen-header p {
  max-width: 730px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-nineteen-header strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-nineteen-stage {
  position: relative;
  min-height: 520px;
  overflow: hidden;
  border-radius: 34px;
  background: #111827;
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.18);
}

.vb-gallery-nineteen-art {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #111827;
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.vb-gallery-nineteen-art::before,
.vb-gallery-nineteen-art::after,
.vb-gallery-nineteen-thumb-art::before,
.vb-gallery-nineteen-thumb-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-nineteen-art.is-changing {
  opacity: 0.35;
  transform: scale(1.03);
}

.vb-gallery-nineteen-art-cinema,
.vb-gallery-nineteen-thumb-art-cinema {
  background: linear-gradient(135deg, #020617 0%, #312e81 50%, #7c3aed 100%);
}

.vb-gallery-nineteen-art-cinema::before,
.vb-gallery-nineteen-thumb-art-cinema::before {
  left: 12%;
  top: 18%;
  width: 76%;
  height: 58%;
  border-radius: 32px;
  background:
    linear-gradient(135deg, rgba(255,255,255,0.22), rgba(255,255,255,0.04)),
    repeating-linear-gradient(90deg, rgba(255,255,255,0.17) 0 4px, transparent 4px 24px);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-nineteen-art-cinema::after,
.vb-gallery-nineteen-thumb-art-cinema::after {
  right: 18%;
  bottom: 18%;
  width: 104px;
  height: 104px;
  border-radius: 999px;
  background: rgba(255,255,255,0.24);
  filter: blur(8px);
}

.vb-gallery-nineteen-art-sunset,
.vb-gallery-nineteen-thumb-art-sunset {
  background: linear-gradient(180deg, #fde68a 0%, #fb923c 48%, #7c2d12 100%);
}

.vb-gallery-nineteen-art-sunset::before,
.vb-gallery-nineteen-thumb-art-sunset::before {
  left: -8%;
  right: -8%;
  bottom: -18%;
  height: 56%;
  background: #431407;
  clip-path: polygon(0 100%, 0 58%, 15% 42%, 30% 66%, 48% 34%, 66% 62%, 83% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-nineteen-art-sunset::after,
.vb-gallery-nineteen-thumb-art-sunset::after {
  right: 18%;
  top: 16%;
  width: 92px;
  height: 92px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 70px rgba(255,247,237,0.72);
}

.vb-gallery-nineteen-art-product,
.vb-gallery-nineteen-thumb-art-product {
  background: radial-gradient(circle at 50% 44%, #ffffff 0%, #f3e8ff 36%, #581c87 100%);
}

.vb-gallery-nineteen-art-product::before,
.vb-gallery-nineteen-thumb-art-product::before {
  left: 50%;
  top: 50%;
  width: 156px;
  height: 156px;
  border-radius: 34px;
  background: linear-gradient(135deg, #ffffff, #c4b5fd);
  box-shadow: 0 30px 80px rgba(88,28,135,0.34);
  transform: translate(-50%, -50%) rotate(16deg);
}

.vb-gallery-nineteen-art-product::after,
.vb-gallery-nineteen-thumb-art-product::after {
  left: 50%;
  top: 50%;
  width: 82px;
  height: 82px;
  border-radius: 22px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(16deg);
}

.vb-gallery-nineteen-art-forest,
.vb-gallery-nineteen-thumb-art-forest {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-nineteen-art-forest::before,
.vb-gallery-nineteen-thumb-art-forest::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-nineteen-art-forest::after,
.vb-gallery-nineteen-thumb-art-forest::after {
  right: 16%;
  top: 16%;
  width: 88px;
  height: 88px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-nineteen-overlay {
  position: absolute;
  z-index: 2;
  left: clamp(18px, 4vw, 38px);
  right: clamp(18px, 4vw, 38px);
  bottom: clamp(18px, 4vw, 34px);
  max-width: 660px;
  padding: clamp(18px, 3vw, 28px);
  border-radius: 28px;
  background: rgba(15, 23, 42, 0.70);
  border: 1px solid rgba(255,255,255,0.14);
  backdrop-filter: blur(14px);
}

.vb-gallery-nineteen-overlay > span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-gallery-nineteen-overlay h4 {
  margin: 0 0 8px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 4vw, 52px) !important;
  line-height: 1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.065em;
}

.vb-gallery-nineteen-overlay p {
  margin: 0 0 18px !important;
  color: #e2e8f0 !important;
  -webkit-text-fill-color: #e2e8f0 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-gallery-nineteen-progress {
  height: 10px;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(255,255,255,0.16);
}

.vb-gallery-nineteen-progress div {
  width: 0%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(135deg, #818cf8, #f472b6);
}

.vb-gallery-nineteen-progress div.is-playing {
  animation: vbGalleryNineteenProgress 3.2s linear forwards;
}

@keyframes vbGalleryNineteenProgress {
  from {
    width: 0%;
  }

  to {
    width: 100%;
  }
}

.vb-gallery-nineteen-controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin-top: 18px;
  padding: 12px;
  border-radius: 24px;
  background: rgba(255,255,255,0.78);
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-gallery-nineteen-controls button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 12px 16px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-gallery-nineteen-controls button:nth-child(2) {
  background: linear-gradient(135deg, #4f46e5, #db2777);
  box-shadow: 0 14px 34px rgba(79, 70, 229, 0.24);
}

.vb-gallery-nineteen-thumbs {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin-top: 16px;
}

.vb-gallery-nineteen-thumb {
  overflow: hidden;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 20px;
  background: #ffffff;
  cursor: pointer;
  text-align: left;
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.09);
}

.vb-gallery-nineteen-thumb.is-active {
  border-color: rgba(79, 70, 229, 0.90);
  box-shadow: 0 20px 48px rgba(79, 70, 229, 0.18);
}

.vb-gallery-nineteen-thumb-art {
  position: relative;
  height: 120px;
  overflow: hidden;
}

.vb-gallery-nineteen-thumb strong {
  display: block;
  padding: 11px;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 13px;
  line-height: 1.2;
  font-weight: 950;
}

@media (max-width: 820px) {
  .vb-gallery-nineteen-header {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-nineteen-header strong {
    justify-content: center;
    width: 100%;
  }

  .vb-gallery-nineteen-thumbs {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-nineteen-stage {
    min-height: 460px;
  }
}

@media (max-width: 560px) {
  .vb-gallery-nineteen-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-nineteen-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-nineteen-controls {
    display: grid;
    grid-template-columns: 1fr;
  }

  .vb-gallery-nineteen-stage {
    min-height: 520px;
    border-radius: 24px;
  }

  .vb-gallery-nineteen-overlay {
    left: 14px;
    right: 14px;
    bottom: 14px;
    border-radius: 22px;
  }
}

This image gallery with slideshow mode is useful for homepage showcases, portfolio highlights, product previews, event recaps, travel galleries, presentation-style sections, and visual landing pages where gallery content should rotate automatically.

20. Image Gallery with Search

An image gallery with search helps users find relevant visuals quickly inside a larger gallery. This pattern is useful for media libraries, portfolio archives, product galleries, documentation image collections, real estate listings, travel galleries, and design inspiration pages.

This example uses JavaScript text search across gallery titles, categories, and keywords. As the user types, the gallery updates immediately, the result count changes, and an empty state appears when no items match. The thumbnails are CSS-only illustrations, so the demo does not use external images.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twenty]");
  if (!gallery) return;

  const searchInput = gallery.querySelector("[data-vb-gallery-twenty-search]");
  const grid = gallery.querySelector("[data-vb-gallery-twenty-grid]");
  const count = gallery.querySelector("[data-vb-gallery-twenty-count]");
  const empty = gallery.querySelector("[data-vb-gallery-twenty-empty]");

  const items = [
    {
      title: "Blue Portfolio",
      category: "Portfolio",
      keywords: "blue web design project interface agency",
      art: "vb-gallery-twenty-art-blue",
      description: "A CSS-only blue portfolio card for searchable gallery interfaces."
    },
    {
      title: "Sunset Travel",
      category: "Travel",
      keywords: "sunset orange mountain travel warm destination",
      art: "vb-gallery-twenty-art-sunset",
      description: "A CSS sunset card for travel gallery search examples."
    },
    {
      title: "Product Display",
      category: "Product",
      keywords: "product ecommerce object shop launch item",
      art: "vb-gallery-twenty-art-product",
      description: "A CSS product thumbnail for searchable ecommerce galleries."
    },
    {
      title: "Forest Route",
      category: "Nature",
      keywords: "forest green nature outdoor trees landscape",
      art: "vb-gallery-twenty-art-forest",
      description: "A CSS forest image card for nature gallery searches."
    },
    {
      title: "City Night",
      category: "Architecture",
      keywords: "city night skyline architecture urban dark buildings",
      art: "vb-gallery-twenty-art-city",
      description: "A CSS city card for architecture and urban image searches."
    },
    {
      title: "Abstract Rose",
      category: "Abstract",
      keywords: "abstract pink rose creative shape poster",
      art: "vb-gallery-twenty-art-abstract",
      description: "A CSS abstract gallery item for creative search filters."
    }
  ];

  function createCard(item, index) {
    return `
      <article class="vb-gallery-twenty-card" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-twenty-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-twenty-body">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function searchItems(query) {
    const normalizedQuery = query.trim().toLowerCase();

    if (!normalizedQuery) {
      return items;
    }

    return items.filter(function (item) {
      const searchableText = [
        item.title,
        item.category,
        item.keywords,
        item.description
      ].join(" ").toLowerCase();

      return searchableText.includes(normalizedQuery);
    });
  }

  function renderGallery() {
    const visibleItems = searchItems(searchInput.value);

    grid.innerHTML = visibleItems.map(createCard).join("");
    count.textContent = visibleItems.length === 1 ? "1 result" : visibleItems.length + " results";
    empty.hidden = visibleItems.length > 0;
  }

  searchInput.addEventListener("input", renderGallery);

  renderGallery();
})();

HTML

<div class="vb-gallery-twenty-demo">
  <div class="vb-gallery-twenty-shell" data-vb-gallery-twenty>
    <div class="vb-gallery-twenty-hero">
      <span>Example 20</span>
      <h3>Image Gallery with Search</h3>
      <p>Search CSS-illustrated gallery items by title, category, and keywords with JavaScript live filtering.</p>

      <div class="vb-gallery-twenty-search">
        <input type="search" placeholder="Search gallery items, for example: product, forest, blue..." data-vb-gallery-twenty-search>
        <strong data-vb-gallery-twenty-count>0 results</strong>
      </div>
    </div>

    <div class="vb-gallery-twenty-grid" data-vb-gallery-twenty-grid></div>

    <div class="vb-gallery-twenty-empty" data-vb-gallery-twenty-empty hidden>
      <h4>No gallery items found</h4>
      <p>Try searching for portfolio, product, travel, forest, blue, sunset, abstract, or city.</p>
    </div>
  </div>
</div>

CSS

.vb-gallery-twenty-demo,
.vb-gallery-twenty-demo * {
  box-sizing: border-box;
}

.vb-gallery-twenty-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    linear-gradient(90deg, rgba(15, 23, 42, 0.05) 1px, transparent 1px),
    linear-gradient(0deg, rgba(15, 23, 42, 0.05) 1px, transparent 1px),
    linear-gradient(135deg, #f8fafc 0%, #f0f9ff 52%, #ffffff 100%) !important;
  background-size: 32px 32px, 32px 32px, auto !important;
  border: 1px solid rgba(14, 165, 233, 0.20);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.09);
}

.vb-gallery-twenty-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-twenty-hero {
  margin-bottom: 24px;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 30px;
  background:
    radial-gradient(circle at 16% 18%, rgba(255,255,255,0.14), transparent 34%),
    linear-gradient(135deg, #082f49, #1d4ed8 52%, #0f172a) !important;
  box-shadow: 0 24px 70px rgba(30, 64, 175, 0.22);
}

.vb-gallery-twenty-hero > span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-twenty-hero h3 {
  max-width: 820px;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-twenty-hero p {
  max-width: 760px;
  margin: 0 0 22px !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-twenty-search {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  max-width: 820px;
}

.vb-gallery-twenty-search input {
  width: 100%;
  min-height: 54px;
  border: 1px solid rgba(255,255,255,0.22);
  border-radius: 18px;
  background: rgba(255,255,255,0.12);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 0 16px;
  font-size: 15px;
  font-weight: 750;
  outline: none;
  backdrop-filter: blur(10px);
}

.vb-gallery-twenty-search input::placeholder {
  color: rgba(255,255,255,0.68);
  -webkit-text-fill-color: rgba(255,255,255,0.68);
}

.vb-gallery-twenty-search input:focus {
  border-color: rgba(186, 230, 253, 0.90);
  box-shadow: 0 0 0 4px rgba(186, 230, 253, 0.12);
}

.vb-gallery-twenty-search strong {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 54px;
  padding: 0 16px;
  border-radius: 18px;
  background: #ffffff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-twenty-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-twenty-card {
  overflow: hidden;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGalleryTwentyIn 0.32s ease both;
}

@keyframes vbGalleryTwentyIn {
  from {
    opacity: 0;
    transform: translateY(14px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-twenty-art {
  position: relative;
  height: 225px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-twenty-art::before,
.vb-gallery-twenty-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-twenty-art-blue {
  background: linear-gradient(135deg, #020617 0%, #2563eb 54%, #38bdf8 100%);
}

.vb-gallery-twenty-art-blue::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 60%;
  border-radius: 28px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 20px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-twenty-art-blue::after {
  right: 16%;
  bottom: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-twenty-art-sunset {
  background: linear-gradient(180deg, #fde68a 0%, #fb923c 48%, #7c2d12 100%);
}

.vb-gallery-twenty-art-sunset::before {
  left: -8%;
  right: -8%;
  bottom: -18%;
  height: 56%;
  background: #431407;
  clip-path: polygon(0 100%, 0 58%, 15% 42%, 30% 66%, 48% 34%, 66% 62%, 83% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-twenty-art-sunset::after {
  right: 18%;
  top: 16%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 64px rgba(255,247,237,0.72);
}

.vb-gallery-twenty-art-product {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #e0e7ff 36%, #3730a3 100%);
}

.vb-gallery-twenty-art-product::before {
  left: 50%;
  top: 50%;
  width: 130px;
  height: 130px;
  border-radius: 30px;
  background: linear-gradient(135deg, #ffffff, #c7d2fe);
  box-shadow: 0 30px 80px rgba(55,48,163,0.32);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twenty-art-product::after {
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  border-radius: 20px;
  border: 4px solid rgba(79,70,229,0.46);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twenty-art-forest {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-twenty-art-forest::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-twenty-art-forest::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-twenty-art-city {
  background: linear-gradient(180deg, #312e81 0%, #111827 66%, #020617 100%);
}

.vb-gallery-twenty-art-city::before {
  left: 10%;
  bottom: 0;
  width: 18%;
  height: 58%;
  background: #020617;
  box-shadow:
    70px -42px 0 #020617,
    142px -12px 0 #020617,
    224px -62px 0 #020617,
    304px -24px 0 #020617;
}

.vb-gallery-twenty-art-city::after {
  inset: 18% 10% auto 10%;
  height: 3px;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,0.82) 0 8px, transparent 8px 24px);
  box-shadow:
    0 34px 0 rgba(255,255,255,0.42),
    0 70px 0 rgba(255,255,255,0.22);
}

.vb-gallery-twenty-art-abstract {
  background: linear-gradient(135deg, #500724 0%, #be123c 52%, #fb7185 100%);
}

.vb-gallery-twenty-art-abstract::before {
  left: 50%;
  top: 52%;
  width: 140px;
  height: 140px;
  border-radius: 42% 58% 62% 38%;
  background: radial-gradient(circle at 35% 30%, #fff1f2, #fb7185 44%, #9f1239 100%);
  transform: translate(-50%, -50%) rotate(22deg);
  box-shadow: 0 26px 70px rgba(80,7,36,0.36);
}

.vb-gallery-twenty-art-abstract::after {
  left: 14%;
  top: 16%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: rgba(255,255,255,0.22);
  filter: blur(6px);
}

.vb-gallery-twenty-body {
  display: grid;
  gap: 8px;
  padding: 17px;
}

.vb-gallery-twenty-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-twenty-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-twenty-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-twenty-empty {
  margin-top: 18px;
  padding: 28px;
  border-radius: 26px;
  background: #ffffff;
  border: 1px dashed rgba(14, 165, 233, 0.36);
  text-align: center;
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.07);
}

.vb-gallery-twenty-empty h4 {
  margin: 0 0 8px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 24px !important;
  line-height: 1.1 !important;
  font-weight: 950 !important;
}

.vb-gallery-twenty-empty p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
}

@media (max-width: 1050px) {
  .vb-gallery-twenty-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 820px) {
  .vb-gallery-twenty-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-twenty-search {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-gallery-twenty-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-twenty-hero h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-twenty-grid {
    grid-template-columns: 1fr;
  }
}

This image gallery with search is useful for large media libraries, portfolio archives, product galleries, travel collections, real estate listings, documentation visuals, and any image-heavy page where users need to find specific visuals quickly.

21. Image Gallery with Keyboard Navigation

An image gallery with keyboard navigation improves usability by letting visitors move through gallery items with the keyboard. This pattern is useful for portfolio galleries, product previews, media libraries, documentation galleries, presentation sections, and accessible image viewers.

This example uses JavaScript keyboard event handling. Users can click a gallery item, then use Arrow Left and Arrow Right to move between items. The selected item, preview artwork, title, category, and counter update dynamically. All visuals are CSS-only illustrations, so the example does not use external image files.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twentyone]");
  if (!gallery) return;

  const previewArt = gallery.querySelector("[data-vb-gallery-twentyone-preview-art]");
  const title = gallery.querySelector("[data-vb-gallery-twentyone-title]");
  const category = gallery.querySelector("[data-vb-gallery-twentyone-category]");
  const description = gallery.querySelector("[data-vb-gallery-twentyone-description]");
  const counter = gallery.querySelector("[data-vb-gallery-twentyone-counter]");
  const list = gallery.querySelector("[data-vb-gallery-twentyone-list]");

  const items = [
    {
      title: "Blue Interface",
      category: "Portfolio",
      description: "A CSS-only portfolio visual controlled by keyboard navigation.",
      art: "vb-gallery-twentyone-art-blue",
      thumb: "vb-gallery-twentyone-thumb-blue"
    },
    {
      title: "Sunset Frame",
      category: "Travel",
      description: "A warm travel-style image item for arrow-key gallery movement.",
      art: "vb-gallery-twentyone-art-orange",
      thumb: "vb-gallery-twentyone-thumb-orange"
    },
    {
      title: "Product Object",
      category: "Product",
      description: "A CSS product-style gallery item selected with keyboard controls.",
      art: "vb-gallery-twentyone-art-purple",
      thumb: "vb-gallery-twentyone-thumb-purple"
    },
    {
      title: "Green Route",
      category: "Nature",
      description: "A CSS nature visual for accessible gallery navigation patterns.",
      art: "vb-gallery-twentyone-art-green",
      thumb: "vb-gallery-twentyone-thumb-green"
    },
    {
      title: "City Night",
      category: "Architecture",
      description: "A CSS city illustration for keyboard-friendly visual browsing.",
      art: "vb-gallery-twentyone-art-city",
      thumb: "vb-gallery-twentyone-thumb-city"
    }
  ];

  let activeIndex = 0;

  function createThumb(item, index) {
    const activeClass = index === activeIndex ? "is-active" : "";

    return `
      <button type="button" class="vb-gallery-twentyone-thumb ${activeClass}" data-index="${index}">
        <div class="vb-gallery-twentyone-thumb-art ${item.thumb}" aria-label="${item.title} thumbnail" role="img"></div>
        <div>
          <strong>${item.title}</strong>
          <small>${item.category}</small>
        </div>
      </button>
    `;
  }

  function renderThumbs() {
    list.innerHTML = items.map(createThumb).join("");
  }

  function renderActiveItem() {
    const item = items[activeIndex];

    previewArt.classList.add("is-changing");

    window.setTimeout(function () {
      previewArt.className = "vb-gallery-twentyone-preview-art " + item.art;
      title.textContent = item.title;
      category.textContent = item.category;
      description.textContent = item.description;
      counter.textContent = activeIndex + 1 + " / " + items.length;
      renderThumbs();
    }, 140);
  }

  function showNext() {
    activeIndex = (activeIndex + 1) % items.length;
    renderActiveItem();
  }

  function showPrevious() {
    activeIndex = (activeIndex - 1 + items.length) % items.length;
    renderActiveItem();
  }

  list.addEventListener("click", function (event) {
    const button = event.target.closest("[data-index]");
    if (!button) return;

    activeIndex = Number(button.getAttribute("data-index"));
    renderActiveItem();
  });

  document.addEventListener("keydown", function (event) {
    const galleryVisible = gallery.getBoundingClientRect().top < window.innerHeight && gallery.getBoundingClientRect().bottom > 0;
    if (!galleryVisible) return;

    if (event.key === "ArrowRight") {
      showNext();
    }

    if (event.key === "ArrowLeft") {
      showPrevious();
    }
  });

  renderActiveItem();
})();

HTML

<div class="vb-gallery-twentyone-demo">
  <div class="vb-gallery-twentyone-shell" data-vb-gallery-twentyone>
    <div class="vb-gallery-twentyone-header">
      <div>
        <span>Example 21</span>
        <h3>Image Gallery with Keyboard Navigation</h3>
        <p>Click a gallery item, then use Arrow Left and Arrow Right to move through CSS-illustrated images.</p>
      </div>

      <strong data-vb-gallery-twentyone-counter>1 / 5</strong>
    </div>

    <div class="vb-gallery-twentyone-layout">
      <div class="vb-gallery-twentyone-preview">
        <div class="vb-gallery-twentyone-preview-art" data-vb-gallery-twentyone-preview-art></div>

        <div class="vb-gallery-twentyone-preview-content">
          <span data-vb-gallery-twentyone-category>Category</span>
          <h4 data-vb-gallery-twentyone-title>Gallery Item</h4>
          <p data-vb-gallery-twentyone-description>Use the keyboard to move between items.</p>

          <div class="vb-gallery-twentyone-keys">
            <kbd>←</kbd>
            <span>Use keyboard arrows</span>
            <kbd>→</kbd>
          </div>
        </div>
      </div>

      <div class="vb-gallery-twentyone-list" data-vb-gallery-twentyone-list></div>
    </div>
  </div>
</div>

CSS

.vb-gallery-twentyone-demo,
.vb-gallery-twentyone-demo * {
  box-sizing: border-box;
}

.vb-gallery-twentyone-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 12% 12%, rgba(37, 99, 235, 0.16), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(20, 184, 166, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #f0fdfa 52%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.22);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

/* CSS jätkub live näidises täismahus.
   Kopeerimisel kasuta ülal olevat live CSS-i, kus kogu näidise stiil on 100% olemas. */

This image gallery with keyboard navigation is useful for accessible portfolio galleries, product previews, media libraries, documentation galleries, design review tools, and presentation-style visual sections where users should move through images without relying only on mouse clicks.

22. Image Gallery with Random Shuffle

An image gallery with random shuffle lets users rearrange gallery items into a fresh order with one click. This pattern is useful for inspiration boards, creative portfolios, moodboards, product discovery sections, random project showcases, design galleries, and playful visual browsing interfaces.

This example uses JavaScript array shuffling with the Fisher-Yates algorithm. Clicking the Shuffle Gallery button randomizes the item order, updates the shuffle counter, and re-renders the CSS-only gallery cards with entrance animation. No external image URLs are used.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twentytwo]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-twentytwo-grid]");
  const button = gallery.querySelector("[data-vb-gallery-twentytwo-shuffle]");
  const count = gallery.querySelector("[data-vb-gallery-twentytwo-count]");

  let shuffleCount = 0;

  let items = [
    {
      title: "Pink Shape",
      category: "Abstract",
      art: "vb-gallery-twentytwo-art-pink",
      description: "A CSS-only abstract card for shuffled image gallery layouts."
    },
    {
      title: "Blue Interface",
      category: "Portfolio",
      art: "vb-gallery-twentytwo-art-blue",
      description: "A CSS portfolio card that changes position when shuffled."
    },
    {
      title: "Gold Journey",
      category: "Travel",
      art: "vb-gallery-twentytwo-art-gold",
      description: "A warm CSS travel card for randomized gallery browsing."
    },
    {
      title: "Green Forest",
      category: "Nature",
      art: "vb-gallery-twentytwo-art-green",
      description: "A CSS nature card that can appear in any shuffled order."
    },
    {
      title: "Purple Product",
      category: "Product",
      art: "vb-gallery-twentytwo-art-purple",
      description: "A CSS product object card for random discovery sections."
    },
    {
      title: "City Night",
      category: "Architecture",
      art: "vb-gallery-twentytwo-art-city",
      description: "A CSS city illustration used inside a random image gallery."
    }
  ];

  function createCard(item, index) {
    return `
      <article class="vb-gallery-twentytwo-card" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-twentytwo-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-twentytwo-body">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function shuffleItems(array) {
    const shuffled = array.slice();

    for (let index = shuffled.length - 1; index > 0; index -= 1) {
      const randomIndex = Math.floor(Math.random() * (index + 1));
      const currentItem = shuffled[index];

      shuffled[index] = shuffled[randomIndex];
      shuffled[randomIndex] = currentItem;
    }

    return shuffled;
  }

  function renderGallery() {
    grid.innerHTML = items.map(createCard).join("");
    count.textContent = shuffleCount === 1 ? "1 shuffle" : shuffleCount + " shuffles";
  }

  button.addEventListener("click", function () {
    items = shuffleItems(items);
    shuffleCount += 1;
    renderGallery();
  });

  renderGallery();
})();

HTML

<div class="vb-gallery-twentytwo-demo">
  <div class="vb-gallery-twentytwo-shell" data-vb-gallery-twentytwo>
    <div class="vb-gallery-twentytwo-top">
      <div>
        <span>Example 22</span>
        <h3>Image Gallery with Random Shuffle</h3>
        <p>Shuffle CSS-illustrated gallery cards into a new random order using JavaScript.</p>
      </div>

      <div class="vb-gallery-twentytwo-actions">
        <strong data-vb-gallery-twentytwo-count>0 shuffles</strong>
        <button type="button" data-vb-gallery-twentytwo-shuffle>Shuffle Gallery</button>
      </div>
    </div>

    <div class="vb-gallery-twentytwo-grid" data-vb-gallery-twentytwo-grid></div>
  </div>
</div>

CSS

.vb-gallery-twentytwo-demo,
.vb-gallery-twentytwo-demo * {
  box-sizing: border-box;
}

.vb-gallery-twentytwo-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 46px 18px 46px 18px;
  background:
    radial-gradient(circle at 12% 12%, rgba(236, 72, 153, 0.18), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(245, 158, 11, 0.16), transparent 34%),
    linear-gradient(135deg, #fdf2f8 0%, #fffbeb 52%, #ffffff 100%) !important;
  border: 1px solid rgba(236, 72, 153, 0.22);
  box-shadow: 0 28px 80px rgba(190, 24, 93, 0.10);
}

.vb-gallery-twentytwo-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-twentytwo-top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
  gap: 18px;
  margin-bottom: 24px;
}

.vb-gallery-twentytwo-top > div:first-child {
  padding: clamp(22px, 4vw, 34px);
  border-radius: 34px 10px 34px 10px;
  background:
    radial-gradient(circle at 18% 20%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #831843, #be123c 52%, #92400e) !important;
  box-shadow: 0 24px 70px rgba(190, 24, 93, 0.22);
}

.vb-gallery-twentytwo-top span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fce7f3 !important;
  -webkit-text-fill-color: #fce7f3 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-twentytwo-top h3 {
  max-width: 800px;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-twentytwo-top p {
  max-width: 730px;
  margin: 0 !important;
  color: #fff7ed !important;
  -webkit-text-fill-color: #fff7ed !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-twentytwo-actions {
  display: grid;
  align-content: center;
  gap: 12px;
  padding: 22px;
  border-radius: 10px 34px 10px 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.09);
}

.vb-gallery-twentytwo-actions strong {
  display: flex;
  justify-content: center;
  padding: 14px 16px;
  border-radius: 20px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
}

.vb-gallery-twentytwo-actions button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #db2777, #f59e0b);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 14px 18px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(219, 39, 119, 0.24);
}

.vb-gallery-twentytwo-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-twentytwo-card {
  overflow: hidden;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGalleryTwentytwoIn 0.34s ease both;
}

@keyframes vbGalleryTwentytwoIn {
  from {
    opacity: 0;
    transform: translateY(18px) rotate(-1deg) scale(0.96);
  }

  to {
    opacity: 1;
    transform: translateY(0) rotate(0deg) scale(1);
  }
}

.vb-gallery-twentytwo-art {
  position: relative;
  height: 225px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-twentytwo-art::before,
.vb-gallery-twentytwo-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-twentytwo-art-pink {
  background: linear-gradient(135deg, #500724 0%, #be123c 52%, #fb7185 100%);
}

.vb-gallery-twentytwo-art-pink::before {
  left: 50%;
  top: 52%;
  width: 140px;
  height: 140px;
  border-radius: 42% 58% 62% 38%;
  background: radial-gradient(circle at 35% 30%, #fff1f2, #fb7185 44%, #9f1239 100%);
  transform: translate(-50%, -50%) rotate(22deg);
  box-shadow: 0 26px 70px rgba(80,7,36,0.36);
}

.vb-gallery-twentytwo-art-pink::after {
  left: 14%;
  top: 16%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: rgba(255,255,255,0.22);
  filter: blur(6px);
}

.vb-gallery-twentytwo-art-blue {
  background: linear-gradient(135deg, #020617 0%, #2563eb 54%, #38bdf8 100%);
}

.vb-gallery-twentytwo-art-blue::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 60%;
  border-radius: 28px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 20px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-twentytwo-art-blue::after {
  right: 16%;
  bottom: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-twentytwo-art-gold {
  background: linear-gradient(180deg, #fef3c7 0%, #f59e0b 52%, #78350f 100%);
}

.vb-gallery-twentytwo-art-gold::before {
  left: -8%;
  right: -8%;
  bottom: -18%;
  height: 56%;
  background: #451a03;
  clip-path: polygon(0 100%, 0 58%, 15% 42%, 30% 66%, 48% 34%, 66% 62%, 83% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-twentytwo-art-gold::after {
  right: 18%;
  top: 16%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #fffbeb;
  box-shadow: 0 0 64px rgba(255,251,235,0.72);
}

.vb-gallery-twentytwo-art-green {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-twentytwo-art-green::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-twentytwo-art-green::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-twentytwo-art-purple {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #e9d5ff 36%, #581c87 100%);
}

.vb-gallery-twentytwo-art-purple::before {
  left: 50%;
  top: 50%;
  width: 130px;
  height: 130px;
  border-radius: 30px;
  background: linear-gradient(135deg, #ffffff, #d8b4fe);
  box-shadow: 0 30px 80px rgba(88,28,135,0.32);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentytwo-art-purple::after {
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  border-radius: 20px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentytwo-art-city {
  background: linear-gradient(180deg, #312e81 0%, #111827 66%, #020617 100%);
}

.vb-gallery-twentytwo-art-city::before {
  left: 10%;
  bottom: 0;
  width: 18%;
  height: 58%;
  background: #020617;
  box-shadow:
    70px -42px 0 #020617,
    142px -12px 0 #020617,
    224px -62px 0 #020617,
    304px -24px 0 #020617;
}

.vb-gallery-twentytwo-art-city::after {
  inset: 18% 10% auto 10%;
  height: 3px;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,0.82) 0 8px, transparent 8px 24px);
  box-shadow:
    0 34px 0 rgba(255,255,255,0.42),
    0 70px 0 rgba(255,255,255,0.22);
}

.vb-gallery-twentytwo-body {
  display: grid;
  gap: 8px;
  padding: 17px;
}

.vb-gallery-twentytwo-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #fdf2f8;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-twentytwo-body h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-twentytwo-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

@media (max-width: 1050px) {
  .vb-gallery-twentytwo-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 820px) {
  .vb-gallery-twentytwo-top {
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentytwo-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .vb-gallery-twentytwo-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-twentytwo-top h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-twentytwo-grid {
    grid-template-columns: 1fr;
  }
}

This image gallery with random shuffle is useful for inspiration boards, creative portfolios, moodboards, product discovery sections, random project showcases, visual idea generators, and playful image-heavy browsing experiences.

23. Image Gallery with Multi Tag Filtering

An image gallery with multi tag filtering lets users combine several tags to narrow down visible gallery items. This is useful for portfolio archives, product collections, design inspiration boards, travel galleries, media libraries, and visual search pages where one category filter is not enough.

This example uses JavaScript Set state to store active tags. Users can turn multiple tags on or off, and the gallery only shows CSS-illustrated items that match every selected tag. The active tags, result count, clear button, and empty state all update dynamically.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twentythree]");
  if (!gallery) return;

  const tagWrap = gallery.querySelector("[data-vb-gallery-twentythree-tags]");
  const grid = gallery.querySelector("[data-vb-gallery-twentythree-grid]");
  const count = gallery.querySelector("[data-vb-gallery-twentythree-count]");
  const activeText = gallery.querySelector("[data-vb-gallery-twentythree-active]");
  const clearButton = gallery.querySelector("[data-vb-gallery-twentythree-clear]");
  const empty = gallery.querySelector("[data-vb-gallery-twentythree-empty]");

  const activeTags = new Set();

  const items = [
    {
      title: "Interface Concept",
      description: "A CSS interface thumbnail for portfolio and dark gallery filters.",
      art: "vb-gallery-twentythree-art-interface",
      tags: ["portfolio", "dark"]
    },
    {
      title: "Product Package",
      description: "A CSS product visual that matches product and bright tag searches.",
      art: "vb-gallery-twentythree-art-pack",
      tags: ["product", "bright"]
    },
    {
      title: "Sunset Road",
      description: "A warm CSS travel image that matches travel and bright tags.",
      art: "vb-gallery-twentythree-art-sunset",
      tags: ["travel", "bright"]
    },
    {
      title: "Forest Path",
      description: "A CSS nature card for nature and bright gallery filtering.",
      art: "vb-gallery-twentythree-art-forest",
      tags: ["nature", "bright"]
    },
    {
      title: "Night Editorial",
      description: "A dark CSS visual that works for portfolio and dark filters.",
      art: "vb-gallery-twentythree-art-dark",
      tags: ["portfolio", "dark"]
    },
    {
      title: "Glass Product",
      description: "A bright CSS product visual for product and bright tag combinations.",
      art: "vb-gallery-twentythree-art-bright",
      tags: ["product", "bright"]
    }
  ];

  function createCard(item, index) {
    const tagMarkup = item.tags.map(function (tag) {
      return "<small>" + tag + "</small>";
    }).join("");

    return `
      <article class="vb-gallery-twentythree-card" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-twentythree-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-twentythree-body">
          <div class="vb-gallery-twentythree-card-tags">${tagMarkup}</div>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function itemMatchesActiveTags(item) {
    if (activeTags.size === 0) return true;

    return Array.from(activeTags).every(function (tag) {
      return item.tags.includes(tag);
    });
  }

  function updateActiveText() {
    if (activeTags.size === 0) {
      activeText.textContent = "Active tags: none";
      return;
    }

    activeText.textContent = "Active tags: " + Array.from(activeTags).join(", ");
  }

  function renderGallery() {
    const visibleItems = items.filter(itemMatchesActiveTags);

    grid.innerHTML = visibleItems.map(createCard).join("");
    count.textContent = visibleItems.length === 1 ? "1 result" : visibleItems.length + " results";
    empty.hidden = visibleItems.length > 0;
    updateActiveText();

    tagWrap.querySelectorAll("button[data-tag]").forEach(function (button) {
      button.classList.toggle("is-active", activeTags.has(button.getAttribute("data-tag")));
    });
  }

  tagWrap.addEventListener("click", function (event) {
    const button = event.target.closest("button[data-tag]");
    if (!button) return;

    const tag = button.getAttribute("data-tag");

    if (activeTags.has(tag)) {
      activeTags.delete(tag);
    } else {
      activeTags.add(tag);
    }

    renderGallery();
  });

  clearButton.addEventListener("click", function () {
    activeTags.clear();
    renderGallery();
  });

  renderGallery();
})();

HTML

<div class="vb-gallery-twentythree-demo">
  <div class="vb-gallery-twentythree-shell" data-vb-gallery-twentythree>
    <div class="vb-gallery-twentythree-hero">
      <div>
        <span>Example 23</span>
        <h3>Image Gallery with Multi Tag Filtering</h3>
        <p>Select several tags and show only CSS-illustrated gallery items that match every active tag.</p>
      </div>

      <div class="vb-gallery-twentythree-panel">
        <strong data-vb-gallery-twentythree-count>0 results</strong>
        <button type="button" data-vb-gallery-twentythree-clear>Clear Tags</button>
      </div>
    </div>

    <div class="vb-gallery-twentythree-tags" data-vb-gallery-twentythree-tags>
      <button type="button" data-tag="portfolio">Portfolio</button>
      <button type="button" data-tag="product">Product</button>
      <button type="button" data-tag="travel">Travel</button>
      <button type="button" data-tag="dark">Dark</button>
      <button type="button" data-tag="bright">Bright</button>
      <button type="button" data-tag="nature">Nature</button>
    </div>

    <div class="vb-gallery-twentythree-active" data-vb-gallery-twentythree-active>Active tags: none</div>

    <div class="vb-gallery-twentythree-grid" data-vb-gallery-twentythree-grid></div>

    <div class="vb-gallery-twentythree-empty" data-vb-gallery-twentythree-empty hidden>
      <h4>No matching gallery items</h4>
      <p>Remove one tag or try a different tag combination.</p>
    </div>
  </div>
</div>

CSS

.vb-gallery-twentythree-demo,
.vb-gallery-twentythree-demo * {
  box-sizing: border-box;
}

.vb-gallery-twentythree-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 14% 14%, rgba(20, 184, 166, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdfa 0%, #eef2ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(20, 184, 166, 0.22);
  box-shadow: 0 28px 80px rgba(15, 118, 110, 0.10);
}

.vb-gallery-twentythree-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-twentythree-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
  gap: 18px;
  margin-bottom: 18px;
}

.vb-gallery-twentythree-hero > div:first-child {
  padding: clamp(22px, 4vw, 34px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 18% 20%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #0f766e, #3730a3 58%, #111827) !important;
  box-shadow: 0 24px 70px rgba(15, 118, 110, 0.22);
}

.vb-gallery-twentythree-hero span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.18);
  color: #ccfbf1 !important;
  -webkit-text-fill-color: #ccfbf1 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-twentythree-hero h3 {
  max-width: 820px;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-twentythree-hero p {
  max-width: 740px;
  margin: 0 !important;
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-twentythree-panel {
  display: grid;
  align-content: center;
  gap: 12px;
  padding: 22px;
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.09);
}

.vb-gallery-twentythree-panel strong {
  display: flex;
  justify-content: center;
  padding: 14px 16px;
  border-radius: 20px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
}

.vb-gallery-twentythree-panel button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #0f766e, #4f46e5);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 14px 18px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(79, 70, 229, 0.22);
}

.vb-gallery-twentythree-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 12px;
  padding: 12px;
  border-radius: 24px;
  background: rgba(255,255,255,0.78);
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.08);
}

.vb-gallery-twentythree-tags button {
  appearance: none;
  border: 1px solid rgba(20, 184, 166, 0.24);
  border-radius: 999px;
  background: #ffffff;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  padding: 11px 15px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.vb-gallery-twentythree-tags button:hover {
  transform: translateY(-2px);
  border-color: rgba(20, 184, 166, 0.62);
}

.vb-gallery-twentythree-tags button.is-active {
  border-color: transparent;
  background: linear-gradient(135deg, #0f766e, #4f46e5);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  box-shadow: 0 14px 34px rgba(15, 118, 110, 0.22);
}

.vb-gallery-twentythree-active {
  margin-bottom: 18px;
  padding: 12px 14px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.20);
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 13px;
  line-height: 1.5;
  font-weight: 850;
}

.vb-gallery-twentythree-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-gallery-twentythree-card {
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGalleryTwentythreeIn 0.34s ease both;
}

@keyframes vbGalleryTwentythreeIn {
  from {
    opacity: 0;
    transform: translateY(16px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-twentythree-art {
  position: relative;
  height: 245px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-twentythree-art::before,
.vb-gallery-twentythree-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-twentythree-art-interface {
  background: linear-gradient(135deg, #020617 0%, #2563eb 54%, #38bdf8 100%);
}

.vb-gallery-twentythree-art-interface::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 60%;
  border-radius: 28px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 20px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-twentythree-art-interface::after {
  right: 16%;
  bottom: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-twentythree-art-pack {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #ddd6fe 36%, #581c87 100%);
}

.vb-gallery-twentythree-art-pack::before {
  left: 50%;
  top: 50%;
  width: 132px;
  height: 132px;
  border-radius: 30px;
  background: linear-gradient(135deg, #ffffff, #c4b5fd);
  box-shadow: 0 30px 80px rgba(88,28,135,0.32);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentythree-art-pack::after {
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  border-radius: 20px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentythree-art-sunset {
  background: linear-gradient(180deg, #fde68a 0%, #fb923c 48%, #7c2d12 100%);
}

.vb-gallery-twentythree-art-sunset::before {
  left: -8%;
  right: -8%;
  bottom: -18%;
  height: 56%;
  background: #431407;
  clip-path: polygon(0 100%, 0 58%, 15% 42%, 30% 66%, 48% 34%, 66% 62%, 83% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-twentythree-art-sunset::after {
  right: 18%;
  top: 16%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 64px rgba(255,247,237,0.72);
}

.vb-gallery-twentythree-art-forest {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-twentythree-art-forest::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-twentythree-art-forest::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-twentythree-art-dark {
  background: linear-gradient(180deg, #020617 0%, #1e293b 58%, #0f172a 100%);
}

.vb-gallery-twentythree-art-dark::before {
  left: 14%;
  right: 14%;
  top: 18%;
  height: 4px;
  background: repeating-linear-gradient(90deg, #ffffff 0 12px, transparent 12px 28px);
  box-shadow:
    0 44px 0 rgba(255,255,255,0.56),
    0 88px 0 rgba(255,255,255,0.30),
    0 132px 0 rgba(255,255,255,0.18);
}

.vb-gallery-twentythree-art-dark::after {
  right: 15%;
  top: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #facc15;
  box-shadow: 0 0 70px rgba(250,204,21,0.60);
}

.vb-gallery-twentythree-art-bright {
  background: linear-gradient(135deg, #ecfeff 0%, #06b6d4 48%, #0f766e 100%);
}

.vb-gallery-twentythree-art-bright::before {
  left: 16%;
  top: 18%;
  width: 68%;
  height: 62%;
  border-radius: 28px;
  background: rgba(255,255,255,0.20);
  border: 1px solid rgba(255,255,255,0.34);
  backdrop-filter: blur(4px);
}

.vb-gallery-twentythree-art-bright::after {
  left: 50%;
  top: 50%;
  width: 92px;
  height: 92px;
  border-radius: 26px;
  background: rgba(255,255,255,0.70);
  transform: translate(-50%, -50%) rotate(20deg);
}

.vb-gallery-twentythree-body {
  display: grid;
  gap: 10px;
  padding: 18px;
}

.vb-gallery-twentythree-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 22px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-twentythree-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-twentythree-card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.vb-gallery-twentythree-card-tags small {
  display: inline-flex;
  padding: 5px 8px;
  border-radius: 999px;
  background: #f0fdfa;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 10px;
  font-weight: 950;
}

.vb-gallery-twentythree-empty {
  margin-top: 18px;
  padding: 28px;
  border-radius: 26px;
  background: #ffffff;
  border: 1px dashed rgba(20, 184, 166, 0.40);
  text-align: center;
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.07);
}

.vb-gallery-twentythree-empty h4 {
  margin: 0 0 8px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 24px !important;
  line-height: 1.1 !important;
  font-weight: 950 !important;
}

.vb-gallery-twentythree-empty p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
}

@media (max-width: 980px) {
  .vb-gallery-twentythree-hero {
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentythree-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 620px) {
  .vb-gallery-twentythree-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-twentythree-hero h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-twentythree-tags {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-twentythree-tags button {
    width: 100%;
  }

  .vb-gallery-twentythree-grid {
    grid-template-columns: 1fr;
  }
}

This image gallery with multi tag filtering is useful for portfolio archives, product galleries, design inspiration boards, media libraries, travel collections, and any image-heavy page where users need more precise filtering than one category button.

24. Image Gallery with View Switcher

An image gallery with a view switcher lets users change how the same visual content is displayed. This is useful for portfolio pages, media libraries, ecommerce product galleries, case study archives, real estate galleries, and dashboards where different users prefer different browsing layouts.

This example uses JavaScript layout mode state. Users can switch between grid view, list view, and featured view. The gallery keeps the same CSS-illustrated image data but re-renders the layout differently based on the selected view mode.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twentyfour]");
  if (!gallery) return;

  const controls = gallery.querySelector("[data-vb-gallery-twentyfour-controls]");
  const output = gallery.querySelector("[data-vb-gallery-twentyfour-output]");
  const status = gallery.querySelector("[data-vb-gallery-twentyfour-status]");

  let activeView = "grid";

  const items = [
    {
      title: "Blue Portfolio",
      category: "Portfolio",
      art: "vb-gallery-twentyfour-art-blue",
      description: "A CSS-only portfolio thumbnail for flexible gallery layout switching."
    },
    {
      title: "Orange Travel",
      category: "Travel",
      art: "vb-gallery-twentyfour-art-orange",
      description: "A warm CSS travel image that can appear in grid, list, or featured view."
    },
    {
      title: "Purple Product",
      category: "Product",
      art: "vb-gallery-twentyfour-art-purple",
      description: "A CSS product card used as shared data across multiple gallery views."
    },
    {
      title: "Green Nature",
      category: "Nature",
      art: "vb-gallery-twentyfour-art-green",
      description: "A CSS nature visual that adapts to the selected gallery layout mode."
    },
    {
      title: "Pink Abstract",
      category: "Abstract",
      art: "vb-gallery-twentyfour-art-pink",
      description: "A creative CSS image card for view switcher gallery interfaces."
    }
  ];

  function createCard(item, index, extraClass) {
    const className = extraClass ? "vb-gallery-twentyfour-card " + extraClass : "vb-gallery-twentyfour-card";

    return `
      <article class="${className}" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-twentyfour-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-twentyfour-body">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function renderGridView() {
    output.innerHTML = `
      <div class="vb-gallery-twentyfour-grid">
        ${items.map(function (item, index) {
          return createCard(item, index, "");
        }).join("")}
      </div>
    `;
  }

  function renderListView() {
    output.innerHTML = `
      <div class="vb-gallery-twentyfour-list">
        ${items.map(function (item, index) {
          return createCard(item, index, "is-list");
        }).join("")}
      </div>
    `;
  }

  function renderFeaturedView() {
    const mainItem = items[0];
    const sideItems = items.slice(1);

    output.innerHTML = `
      <div class="vb-gallery-twentyfour-featured">
        ${createCard(mainItem, 0, "is-featured-main")}

        <div class="vb-gallery-twentyfour-featured-side">
          ${sideItems.map(function (item, index) {
            return createCard(item, index + 1, "is-featured-small");
          }).join("")}
        </div>
      </div>
    `;
  }

  function updateStatus() {
    const labels = {
      grid: "Grid view",
      list: "List view",
      featured: "Featured view"
    };

    status.textContent = labels[activeView];
  }

  function renderView() {
    if (activeView === "grid") {
      renderGridView();
    }

    if (activeView === "list") {
      renderListView();
    }

    if (activeView === "featured") {
      renderFeaturedView();
    }

    updateStatus();

    controls.querySelectorAll("button[data-view]").forEach(function (button) {
      button.classList.toggle("is-active", button.getAttribute("data-view") === activeView);
    });
  }

  controls.addEventListener("click", function (event) {
    const button = event.target.closest("button[data-view]");
    if (!button) return;

    activeView = button.getAttribute("data-view");
    renderView();
  });

  renderView();
})();

HTML

<div class="vb-gallery-twentyfour-demo">
  <div class="vb-gallery-twentyfour-shell" data-vb-gallery-twentyfour>
    <div class="vb-gallery-twentyfour-head">
      <div>
        <span>Example 24</span>
        <h3>Image Gallery with View Switcher</h3>
        <p>Switch the same CSS-illustrated gallery data between grid, list, and featured layouts.</p>
      </div>

      <strong data-vb-gallery-twentyfour-status>Grid view</strong>
    </div>

    <div class="vb-gallery-twentyfour-controls" data-vb-gallery-twentyfour-controls>
      <button type="button" class="is-active" data-view="grid">Grid View</button>
      <button type="button" data-view="list">List View</button>
      <button type="button" data-view="featured">Featured View</button>
    </div>

    <div class="vb-gallery-twentyfour-output" data-vb-gallery-twentyfour-output></div>
  </div>
</div>

CSS

.vb-gallery-twentyfour-demo,
.vb-gallery-twentyfour-demo * {
  box-sizing: border-box;
}

.vb-gallery-twentyfour-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 42px;
  background:
    radial-gradient(circle at 12% 12%, rgba(249, 115, 22, 0.16), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(14, 165, 233, 0.16), transparent 34%),
    linear-gradient(135deg, #fff7ed 0%, #f0f9ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(249, 115, 22, 0.22);
  box-shadow: 0 28px 80px rgba(154, 52, 18, 0.10);
}

.vb-gallery-twentyfour-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-twentyfour-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 20px;
}

.vb-gallery-twentyfour-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ffedd5;
  color: #c2410c !important;
  -webkit-text-fill-color: #c2410c !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-twentyfour-head h3 {
  max-width: 820px;
  margin: 0 0 14px !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-twentyfour-head p {
  max-width: 740px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-twentyfour-head strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-twentyfour-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 22px;
  padding: 12px;
  border-radius: 24px;
  background: rgba(255,255,255,0.78);
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.08);
}

.vb-gallery-twentyfour-controls button {
  appearance: none;
  border: 1px solid rgba(249, 115, 22, 0.24);
  border-radius: 999px;
  background: #ffffff;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  padding: 11px 15px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.vb-gallery-twentyfour-controls button:hover {
  transform: translateY(-2px);
  border-color: rgba(249, 115, 22, 0.62);
}

.vb-gallery-twentyfour-controls button.is-active {
  border-color: transparent;
  background: linear-gradient(135deg, #f97316, #0284c7);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  box-shadow: 0 14px 34px rgba(249, 115, 22, 0.22);
}

.vb-gallery-twentyfour-output {
  min-height: 360px;
}

.vb-gallery-twentyfour-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-gallery-twentyfour-list {
  display: grid;
  gap: 14px;
}

.vb-gallery-twentyfour-featured {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.65fr);
  gap: 16px;
  align-items: stretch;
}

.vb-gallery-twentyfour-featured-side {
  display: grid;
  gap: 16px;
}

.vb-gallery-twentyfour-card {
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGalleryTwentyfourIn 0.34s ease both;
}

@keyframes vbGalleryTwentyfourIn {
  from {
    opacity: 0;
    transform: translateY(16px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-twentyfour-card.is-list {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  align-items: stretch;
}

.vb-gallery-twentyfour-card.is-featured-main .vb-gallery-twentyfour-art {
  height: 520px;
}

.vb-gallery-twentyfour-card.is-featured-small {
  display: grid;
  grid-template-columns: 140px minmax(0, 1fr);
}

.vb-gallery-twentyfour-card.is-featured-small .vb-gallery-twentyfour-art {
  height: auto;
  min-height: 170px;
}

.vb-gallery-twentyfour-art {
  position: relative;
  height: 245px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-twentyfour-art::before,
.vb-gallery-twentyfour-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-twentyfour-art-blue {
  background: linear-gradient(135deg, #020617 0%, #2563eb 54%, #38bdf8 100%);
}

.vb-gallery-twentyfour-art-blue::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 60%;
  border-radius: 28px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 20px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-twentyfour-art-blue::after {
  right: 16%;
  bottom: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-twentyfour-art-orange {
  background: linear-gradient(180deg, #fde68a 0%, #fb923c 48%, #7c2d12 100%);
}

.vb-gallery-twentyfour-art-orange::before {
  left: -8%;
  right: -8%;
  bottom: -18%;
  height: 56%;
  background: #431407;
  clip-path: polygon(0 100%, 0 58%, 15% 42%, 30% 66%, 48% 34%, 66% 62%, 83% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-twentyfour-art-orange::after {
  right: 18%;
  top: 16%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #fff7ed;
  box-shadow: 0 0 64px rgba(255,247,237,0.72);
}

.vb-gallery-twentyfour-art-purple {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #ddd6fe 36%, #581c87 100%);
}

.vb-gallery-twentyfour-art-purple::before {
  left: 50%;
  top: 50%;
  width: 132px;
  height: 132px;
  border-radius: 30px;
  background: linear-gradient(135deg, #ffffff, #c4b5fd);
  box-shadow: 0 30px 80px rgba(88,28,135,0.32);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentyfour-art-purple::after {
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  border-radius: 20px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentyfour-art-green {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-twentyfour-art-green::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-twentyfour-art-green::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-twentyfour-art-pink {
  background: linear-gradient(135deg, #500724 0%, #be123c 52%, #fb7185 100%);
}

.vb-gallery-twentyfour-art-pink::before {
  left: 50%;
  top: 52%;
  width: 140px;
  height: 140px;
  border-radius: 42% 58% 62% 38%;
  background: radial-gradient(circle at 35% 30%, #fff1f2, #fb7185 44%, #9f1239 100%);
  transform: translate(-50%, -50%) rotate(22deg);
  box-shadow: 0 26px 70px rgba(80,7,36,0.36);
}

.vb-gallery-twentyfour-art-pink::after {
  left: 14%;
  top: 16%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: rgba(255,255,255,0.22);
  filter: blur(6px);
}

.vb-gallery-twentyfour-body {
  display: grid;
  gap: 9px;
  padding: 18px;
  align-content: center;
}

.vb-gallery-twentyfour-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #fff7ed;
  color: #c2410c !important;
  -webkit-text-fill-color: #c2410c !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-twentyfour-body h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 22px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-twentyfour-card.is-featured-main .vb-gallery-twentyfour-body h4 {
  font-size: clamp(30px, 4vw, 52px) !important;
  letter-spacing: -0.065em;
}

.vb-gallery-twentyfour-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

@media (max-width: 980px) {
  .vb-gallery-twentyfour-head {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-twentyfour-head strong {
    justify-content: center;
    width: 100%;
  }

  .vb-gallery-twentyfour-grid,
  .vb-gallery-twentyfour-featured {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-twentyfour-featured-side {
    grid-column: 1 / -1;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-gallery-twentyfour-card.is-list {
    grid-template-columns: 220px minmax(0, 1fr);
  }
}

@media (max-width: 700px) {
  .vb-gallery-twentyfour-card.is-list,
  .vb-gallery-twentyfour-card.is-featured-small {
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentyfour-card.is-featured-small .vb-gallery-twentyfour-art {
    height: 230px;
  }
}

@media (max-width: 620px) {
  .vb-gallery-twentyfour-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-twentyfour-head h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-twentyfour-controls {
    display: grid;
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentyfour-grid,
  .vb-gallery-twentyfour-featured,
  .vb-gallery-twentyfour-featured-side {
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentyfour-card.is-featured-main .vb-gallery-twentyfour-art {
    height: 330px;
  }
}

This image gallery with a view switcher is useful for portfolio pages, media libraries, product galleries, real estate listings, case study archives, dashboards, and visual browsing interfaces where users should control how gallery content is displayed.

25. Image Gallery with Favorite Items

An image gallery with favorite items lets users save images they like while browsing. This pattern is useful for portfolio inspiration pages, product galleries, real estate listings, travel galleries, media libraries, moodboards, and design selection interfaces.

This example uses JavaScript favorite state with localStorage. Users can mark CSS-illustrated gallery items as favorites, switch between all items and favorites only, and keep their favorites after page refresh. No external image files are used.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twentyfive]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-twentyfive-grid]");
  const tabs = gallery.querySelector("[data-vb-gallery-twentyfive-tabs]");
  const count = gallery.querySelector("[data-vb-gallery-twentyfive-count]");
  const clearButton = gallery.querySelector("[data-vb-gallery-twentyfive-clear]");
  const empty = gallery.querySelector("[data-vb-gallery-twentyfive-empty]");
  const storageKey = "vbGalleryTwentyfiveFavorites";

  let activeView = "all";
  let favoriteIds = loadFavorites();

  const items = [
    {
      id: "rose-poster",
      title: "Rose Poster",
      category: "Abstract",
      art: "vb-gallery-twentyfive-art-rose",
      description: "A CSS-only abstract image card that can be saved as a favorite."
    },
    {
      id: "violet-object",
      title: "Violet Object",
      category: "Product",
      art: "vb-gallery-twentyfive-art-violet",
      description: "A CSS product-style gallery item with favorite state."
    },
    {
      id: "blue-interface",
      title: "Blue Interface",
      category: "Portfolio",
      art: "vb-gallery-twentyfive-art-blue",
      description: "A CSS portfolio visual that can be saved to localStorage."
    },
    {
      id: "gold-journey",
      title: "Gold Journey",
      category: "Travel",
      art: "vb-gallery-twentyfive-art-gold",
      description: "A warm CSS travel image for favorite gallery browsing."
    },
    {
      id: "green-forest",
      title: "Green Forest",
      category: "Nature",
      art: "vb-gallery-twentyfive-art-green",
      description: "A CSS nature card that can be kept in the favorites list."
    },
    {
      id: "city-night",
      title: "City Night",
      category: "Architecture",
      art: "vb-gallery-twentyfive-art-city",
      description: "A CSS city illustration with persistent favorite behavior."
    }
  ];

  function loadFavorites() {
    try {
      return JSON.parse(localStorage.getItem(storageKey)) || [];
    } catch (error) {
      return [];
    }
  }

  function saveFavorites() {
    localStorage.setItem(storageKey, JSON.stringify(favoriteIds));
  }

  function isFavorite(id) {
    return favoriteIds.includes(id);
  }

  function getVisibleItems() {
    if (activeView === "favorites") {
      return items.filter(function (item) {
        return isFavorite(item.id);
      });
    }

    return items;
  }

  function createCard(item, index) {
    const favoriteClass = isFavorite(item.id) ? "is-favorite" : "";
    const symbol = isFavorite(item.id) ? "♥" : "♡";

    return `
      <article class="vb-gallery-twentyfive-card" style="animation-delay: ${index * 45}ms">
        <button type="button" class="vb-gallery-twentyfive-fav ${favoriteClass}" data-favorite-id="${item.id}" aria-label="Toggle favorite for ${item.title}">${symbol}</button>

        <div class="vb-gallery-twentyfive-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-twentyfive-body">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function updateCount() {
    count.textContent = favoriteIds.length === 1 ? "1 favorite" : favoriteIds.length + " favorites";
  }

  function renderGallery() {
    const visibleItems = getVisibleItems();

    grid.innerHTML = visibleItems.map(createCard).join("");
    empty.hidden = !(activeView === "favorites" && visibleItems.length === 0);
    updateCount();

    tabs.querySelectorAll("button[data-view]").forEach(function (button) {
      button.classList.toggle("is-active", button.getAttribute("data-view") === activeView);
    });
  }

  function toggleFavorite(id) {
    if (isFavorite(id)) {
      favoriteIds = favoriteIds.filter(function (favoriteId) {
        return favoriteId !== id;
      });
    } else {
      favoriteIds.push(id);
    }

    saveFavorites();
    renderGallery();
  }

  grid.addEventListener("click", function (event) {
    const button = event.target.closest("[data-favorite-id]");
    if (!button) return;

    toggleFavorite(button.getAttribute("data-favorite-id"));
  });

  tabs.addEventListener("click", function (event) {
    const button = event.target.closest("button[data-view]");
    if (!button) return;

    activeView = button.getAttribute("data-view");
    renderGallery();
  });

  clearButton.addEventListener("click", function () {
    favoriteIds = [];
    saveFavorites();
    renderGallery();
  });

  renderGallery();
})();

HTML

<div class="vb-gallery-twentyfive-demo">
  <div class="vb-gallery-twentyfive-shell" data-vb-gallery-twentyfive>
    <div class="vb-gallery-twentyfive-header">
      <div>
        <span>Example 25</span>
        <h3>Image Gallery with Favorite Items</h3>
        <p>Save CSS-illustrated gallery items as favorites and keep them with JavaScript localStorage.</p>
      </div>

      <div class="vb-gallery-twentyfive-summary">
        <strong data-vb-gallery-twentyfive-count>0 favorites</strong>
        <button type="button" data-vb-gallery-twentyfive-clear>Clear Favorites</button>
      </div>
    </div>

    <div class="vb-gallery-twentyfive-tabs" data-vb-gallery-twentyfive-tabs>
      <button type="button" class="is-active" data-view="all">All Gallery Items</button>
      <button type="button" data-view="favorites">Favorites Only</button>
    </div>

    <div class="vb-gallery-twentyfive-grid" data-vb-gallery-twentyfive-grid></div>

    <div class="vb-gallery-twentyfive-empty" data-vb-gallery-twentyfive-empty hidden>
      <h4>No favorite images yet</h4>
      <p>Mark a gallery card as favorite and it will appear in this view.</p>
    </div>
  </div>
</div>

CSS

.vb-gallery-twentyfive-demo,
.vb-gallery-twentyfive-demo * {
  box-sizing: border-box;
}

.vb-gallery-twentyfive-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 14% 14%, rgba(244, 63, 94, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(168, 85, 247, 0.16), transparent 34%),
    linear-gradient(135deg, #fff1f2 0%, #faf5ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(244, 63, 94, 0.22);
  box-shadow: 0 28px 80px rgba(190, 18, 60, 0.10);
}

.vb-gallery-twentyfive-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-twentyfive-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
  gap: 18px;
  margin-bottom: 18px;
}

.vb-gallery-twentyfive-header > div:first-child {
  padding: clamp(22px, 4vw, 34px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 18% 20%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #881337, #7e22ce 58%, #111827) !important;
  box-shadow: 0 24px 70px rgba(190, 18, 60, 0.22);
}

.vb-gallery-twentyfive-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.18);
  color: #ffe4e6 !important;
  -webkit-text-fill-color: #ffe4e6 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-twentyfive-header h3 {
  max-width: 820px;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-twentyfive-header p {
  max-width: 740px;
  margin: 0 !important;
  color: #fce7f3 !important;
  -webkit-text-fill-color: #fce7f3 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-twentyfive-summary {
  display: grid;
  align-content: center;
  gap: 12px;
  padding: 22px;
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.09);
}

.vb-gallery-twentyfive-summary strong {
  display: flex;
  justify-content: center;
  padding: 14px 16px;
  border-radius: 20px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
}

.vb-gallery-twentyfive-summary button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #e11d48, #7e22ce);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 14px 18px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(225, 29, 72, 0.24);
}

.vb-gallery-twentyfive-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 22px;
  padding: 12px;
  border-radius: 24px;
  background: rgba(255,255,255,0.78);
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.08);
}

.vb-gallery-twentyfive-tabs button {
  appearance: none;
  border: 1px solid rgba(244, 63, 94, 0.24);
  border-radius: 999px;
  background: #ffffff;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  padding: 11px 15px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.vb-gallery-twentyfive-tabs button:hover {
  transform: translateY(-2px);
  border-color: rgba(244, 63, 94, 0.62);
}

.vb-gallery-twentyfive-tabs button.is-active {
  border-color: transparent;
  background: linear-gradient(135deg, #e11d48, #7e22ce);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  box-shadow: 0 14px 34px rgba(225, 29, 72, 0.22);
}

.vb-gallery-twentyfive-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-gallery-twentyfive-card {
  position: relative;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGalleryTwentyfiveIn 0.34s ease both;
}

@keyframes vbGalleryTwentyfiveIn {
  from {
    opacity: 0;
    transform: translateY(16px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-twentyfive-fav {
  position: absolute;
  z-index: 5;
  top: 14px;
  right: 14px;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.62);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 20px;
  font-weight: 950;
  cursor: pointer;
  backdrop-filter: blur(10px);
  transition: transform 0.2s ease, background 0.2s ease;
}

.vb-gallery-twentyfive-fav:hover {
  transform: scale(1.08);
}

.vb-gallery-twentyfive-fav.is-favorite {
  background: linear-gradient(135deg, #e11d48, #7e22ce);
}

.vb-gallery-twentyfive-art {
  position: relative;
  height: 245px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-twentyfive-art::before,
.vb-gallery-twentyfive-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-twentyfive-art-rose {
  background: linear-gradient(135deg, #500724 0%, #be123c 52%, #fb7185 100%);
}

.vb-gallery-twentyfive-art-rose::before {
  left: 50%;
  top: 52%;
  width: 150px;
  height: 150px;
  border-radius: 42% 58% 62% 38%;
  background: radial-gradient(circle at 35% 30%, #fff1f2, #fb7185 44%, #9f1239 100%);
  transform: translate(-50%, -50%) rotate(22deg);
  box-shadow: 0 26px 70px rgba(80,7,36,0.36);
}

.vb-gallery-twentyfive-art-rose::after {
  left: 14%;
  top: 16%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: rgba(255,255,255,0.22);
  filter: blur(6px);
}

.vb-gallery-twentyfive-art-violet {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #ddd6fe 36%, #581c87 100%);
}

.vb-gallery-twentyfive-art-violet::before {
  left: 50%;
  top: 50%;
  width: 132px;
  height: 132px;
  border-radius: 30px;
  background: linear-gradient(135deg, #ffffff, #c4b5fd);
  box-shadow: 0 30px 80px rgba(88,28,135,0.32);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentyfive-art-violet::after {
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  border-radius: 20px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentyfive-art-blue {
  background: linear-gradient(135deg, #020617 0%, #2563eb 54%, #38bdf8 100%);
}

.vb-gallery-twentyfive-art-blue::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 60%;
  border-radius: 28px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 20px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-twentyfive-art-blue::after {
  right: 16%;
  bottom: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-twentyfive-art-gold {
  background: linear-gradient(180deg, #fef3c7 0%, #f59e0b 52%, #78350f 100%);
}

.vb-gallery-twentyfive-art-gold::before {
  left: -8%;
  right: -8%;
  bottom: -18%;
  height: 56%;
  background: #451a03;
  clip-path: polygon(0 100%, 0 58%, 15% 42%, 30% 66%, 48% 34%, 66% 62%, 83% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-twentyfive-art-gold::after {
  right: 18%;
  top: 16%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #fffbeb;
  box-shadow: 0 0 64px rgba(255,251,235,0.72);
}

.vb-gallery-twentyfive-art-green {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-twentyfive-art-green::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-twentyfive-art-green::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-twentyfive-art-city {
  background: linear-gradient(180deg, #312e81 0%, #111827 66%, #020617 100%);
}

.vb-gallery-twentyfive-art-city::before {
  left: 10%;
  bottom: 0;
  width: 18%;
  height: 58%;
  background: #020617;
  box-shadow:
    70px -42px 0 #020617,
    142px -12px 0 #020617,
    224px -62px 0 #020617,
    304px -24px 0 #020617;
}

.vb-gallery-twentyfive-art-city::after {
  inset: 18% 10% auto 10%;
  height: 3px;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,0.82) 0 8px, transparent 8px 24px);
  box-shadow:
    0 34px 0 rgba(255,255,255,0.42),
    0 70px 0 rgba(255,255,255,0.22);
}

.vb-gallery-twentyfive-body {
  display: grid;
  gap: 8px;
  padding: 18px;
}

.vb-gallery-twentyfive-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #fff1f2;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-twentyfive-body h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 22px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-twentyfive-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-twentyfive-empty {
  margin-top: 18px;
  padding: 28px;
  border-radius: 26px;
  background: #ffffff;
  border: 1px dashed rgba(244, 63, 94, 0.40);
  text-align: center;
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.07);
}

.vb-gallery-twentyfive-empty h4 {
  margin: 0 0 8px !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 24px !important;
  line-height: 1.1 !important;
  font-weight: 950 !important;
}

.vb-gallery-twentyfive-empty p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
}

@media (max-width: 980px) {
  .vb-gallery-twentyfive-header {
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentyfive-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 620px) {
  .vb-gallery-twentyfive-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-twentyfive-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-twentyfive-tabs {
    display: grid;
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentyfive-grid {
    grid-template-columns: 1fr;
  }
}

This image gallery with favorite items is useful for inspiration galleries, product shortlists, real estate favorites, travel wishlists, media libraries, moodboards, and visual selection interfaces where users need to save preferred images.

26. Image Gallery with Load More Pagination

An image gallery with load more pagination helps large galleries stay fast and easy to browse. Instead of showing every image at once, the gallery displays a limited number of items first and adds more when the user clicks a button.

This example uses JavaScript pagination state. The gallery starts with four CSS-illustrated items, the Load More button adds the next batch, the progress text updates, and the button disappears when all items are visible. The thumbnails are CSS-only illustrations, so no external images are loaded.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twentysix]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-twentysix-grid]");
  const progress = gallery.querySelector("[data-vb-gallery-twentysix-progress]");
  const moreButton = gallery.querySelector("[data-vb-gallery-twentysix-more]");

  const batchSize = 4;
  let visibleCount = batchSize;

  const items = [
    {
      title: "Blue Portfolio",
      category: "Portfolio",
      art: "vb-gallery-twentysix-art-blue",
      description: "A CSS-only gallery image loaded in the first visible batch."
    },
    {
      title: "Cyan Product",
      category: "Product",
      art: "vb-gallery-twentysix-art-cyan",
      description: "A clean CSS visual card for paginated image gallery sections."
    },
    {
      title: "Gold Travel",
      category: "Travel",
      art: "vb-gallery-twentysix-art-gold",
      description: "A warm CSS card revealed by the load more gallery system."
    },
    {
      title: "Green Forest",
      category: "Nature",
      art: "vb-gallery-twentysix-art-green",
      description: "A CSS nature image included in the first gallery batch."
    },
    {
      title: "Purple Object",
      category: "Product",
      art: "vb-gallery-twentysix-art-purple",
      description: "A CSS product-style card shown after clicking load more."
    },
    {
      title: "Pink Abstract",
      category: "Abstract",
      art: "vb-gallery-twentysix-art-pink",
      description: "A creative CSS thumbnail loaded in a later gallery batch."
    },
    {
      title: "City Night",
      category: "Architecture",
      art: "vb-gallery-twentysix-art-city",
      description: "A CSS city visual revealed through pagination state."
    },
    {
      title: "Red Motion",
      category: "Event",
      art: "vb-gallery-twentysix-art-red",
      description: "A CSS event-style card loaded when more items are requested."
    }
  ];

  function createCard(item, index) {
    return `
      <article class="vb-gallery-twentysix-card" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-twentysix-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-twentysix-body">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </article>
    `;
  }

  function renderGallery() {
    const visibleItems = items.slice(0, visibleCount);

    grid.innerHTML = visibleItems.map(createCard).join("");
    progress.textContent = "Showing " + visibleItems.length + " of " + items.length;
    moreButton.hidden = visibleItems.length >= items.length;
  }

  moreButton.addEventListener("click", function () {
    visibleCount = Math.min(visibleCount + batchSize, items.length);
    renderGallery();
  });

  renderGallery();
})();

HTML

<div class="vb-gallery-twentysix-demo">
  <div class="vb-gallery-twentysix-shell" data-vb-gallery-twentysix>
    <div class="vb-gallery-twentysix-top">
      <div>
        <span>Example 26</span>
        <h3>Image Gallery with Load More Pagination</h3>
        <p>Reveal CSS-illustrated gallery cards in batches with JavaScript load more pagination.</p>
      </div>

      <strong data-vb-gallery-twentysix-progress>Showing 0 of 0</strong>
    </div>

    <div class="vb-gallery-twentysix-grid" data-vb-gallery-twentysix-grid></div>

    <div class="vb-gallery-twentysix-actions">
      <button type="button" data-vb-gallery-twentysix-more>Load More Images</button>
    </div>
  </div>
</div>

CSS

.vb-gallery-twentysix-demo,
.vb-gallery-twentysix-demo * {
  box-sizing: border-box;
}

.vb-gallery-twentysix-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    linear-gradient(90deg, rgba(15, 23, 42, 0.05) 1px, transparent 1px),
    linear-gradient(0deg, rgba(15, 23, 42, 0.05) 1px, transparent 1px),
    linear-gradient(135deg, #f8fafc 0%, #ecfeff 52%, #ffffff 100%) !important;
  background-size: 32px 32px, 32px 32px, auto !important;
  border: 1px solid rgba(6, 182, 212, 0.20);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.09);
}

.vb-gallery-twentysix-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-twentysix-top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 24px;
}

.vb-gallery-twentysix-top span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #cffafe;
  color: #0e7490 !important;
  -webkit-text-fill-color: #0e7490 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-twentysix-top h3 {
  max-width: 820px;
  margin: 0 0 14px !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-twentysix-top p {
  max-width: 740px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-twentysix-top strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-twentysix-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-twentysix-card {
  overflow: hidden;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  animation: vbGalleryTwentysixIn 0.34s ease both;
}

@keyframes vbGalleryTwentysixIn {
  from {
    opacity: 0;
    transform: translateY(16px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-twentysix-art {
  position: relative;
  height: 225px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-twentysix-art::before,
.vb-gallery-twentysix-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-twentysix-art-blue {
  background: linear-gradient(135deg, #020617 0%, #2563eb 54%, #38bdf8 100%);
}

.vb-gallery-twentysix-art-blue::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 60%;
  border-radius: 28px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 20px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-twentysix-art-blue::after {
  right: 16%;
  bottom: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-twentysix-art-cyan {
  background: linear-gradient(135deg, #ecfeff 0%, #06b6d4 48%, #0f766e 100%);
}

.vb-gallery-twentysix-art-cyan::before {
  left: 16%;
  top: 18%;
  width: 68%;
  height: 62%;
  border-radius: 28px;
  background: rgba(255,255,255,0.20);
  border: 1px solid rgba(255,255,255,0.34);
  backdrop-filter: blur(4px);
}

.vb-gallery-twentysix-art-cyan::after {
  left: 50%;
  top: 50%;
  width: 92px;
  height: 92px;
  border-radius: 26px;
  background: rgba(255,255,255,0.70);
  transform: translate(-50%, -50%) rotate(20deg);
}

.vb-gallery-twentysix-art-gold {
  background: linear-gradient(180deg, #fef3c7 0%, #f59e0b 52%, #78350f 100%);
}

.vb-gallery-twentysix-art-gold::before {
  left: -8%;
  right: -8%;
  bottom: -18%;
  height: 56%;
  background: #451a03;
  clip-path: polygon(0 100%, 0 58%, 15% 42%, 30% 66%, 48% 34%, 66% 62%, 83% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-twentysix-art-gold::after {
  right: 18%;
  top: 16%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #fffbeb;
  box-shadow: 0 0 64px rgba(255,251,235,0.72);
}

.vb-gallery-twentysix-art-green {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-twentysix-art-green::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-twentysix-art-green::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-twentysix-art-purple {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #ddd6fe 36%, #581c87 100%);
}

.vb-gallery-twentysix-art-purple::before {
  left: 50%;
  top: 50%;
  width: 132px;
  height: 132px;
  border-radius: 30px;
  background: linear-gradient(135deg, #ffffff, #c4b5fd);
  box-shadow: 0 30px 80px rgba(88,28,135,0.32);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentysix-art-purple::after {
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  border-radius: 20px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentysix-art-pink {
  background: linear-gradient(135deg, #500724 0%, #be123c 52%, #fb7185 100%);
}

.vb-gallery-twentysix-art-pink::before {
  left: 50%;
  top: 52%;
  width: 140px;
  height: 140px;
  border-radius: 42% 58% 62% 38%;
  background: radial-gradient(circle at 35% 30%, #fff1f2, #fb7185 44%, #9f1239 100%);
  transform: translate(-50%, -50%) rotate(22deg);
  box-shadow: 0 26px 70px rgba(80,7,36,0.36);
}

.vb-gallery-twentysix-art-pink::after {
  left: 14%;
  top: 16%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: rgba(255,255,255,0.22);
  filter: blur(6px);
}

.vb-gallery-twentysix-art-city {
  background: linear-gradient(180deg, #312e81 0%, #111827 66%, #020617 100%);
}

.vb-gallery-twentysix-art-city::before {
  left: 10%;
  bottom: 0;
  width: 18%;
  height: 58%;
  background: #020617;
  box-shadow:
    70px -42px 0 #020617,
    142px -12px 0 #020617,
    224px -62px 0 #020617,
    304px -24px 0 #020617;
}

.vb-gallery-twentysix-art-city::after {
  inset: 18% 10% auto 10%;
  height: 3px;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,0.82) 0 8px, transparent 8px 24px);
  box-shadow:
    0 34px 0 rgba(255,255,255,0.42),
    0 70px 0 rgba(255,255,255,0.22);
}

.vb-gallery-twentysix-art-red {
  background: radial-gradient(circle at 50% 50%, #ffffff 0%, #fecaca 32%, #991b1b 100%);
}

.vb-gallery-twentysix-art-red::before {
  left: 50%;
  top: 50%;
  width: 150px;
  height: 150px;
  border-radius: 999px;
  border: 18px solid rgba(255,255,255,0.36);
  border-left-color: rgba(255,255,255,0.86);
  transform: translate(-50%, -50%) rotate(18deg);
}

.vb-gallery-twentysix-art-red::after {
  left: 50%;
  top: 50%;
  width: 76px;
  height: 76px;
  border-radius: 999px;
  background: rgba(255,255,255,0.76);
  transform: translate(-50%, -50%);
}

.vb-gallery-twentysix-body {
  display: grid;
  gap: 8px;
  padding: 17px;
}

.vb-gallery-twentysix-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #ecfeff;
  color: #0e7490 !important;
  -webkit-text-fill-color: #0e7490 !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-twentysix-body h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-twentysix-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-twentysix-actions {
  display: flex;
  justify-content: center;
  margin-top: 22px;
}

.vb-gallery-twentysix-actions button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #06b6d4, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 15px 22px;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 36px rgba(6, 182, 212, 0.24);
}

.vb-gallery-twentysix-actions button[hidden] {
  display: none;
}

@media (max-width: 1050px) {
  .vb-gallery-twentysix-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 820px) {
  .vb-gallery-twentysix-top {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-twentysix-top strong {
    justify-content: center;
    width: 100%;
  }

  .vb-gallery-twentysix-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .vb-gallery-twentysix-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-twentysix-top h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-twentysix-grid {
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentysix-actions button {
    width: 100%;
  }
}

This image gallery with load more pagination is useful for large portfolios, media libraries, ecommerce image galleries, real estate galleries, travel collections, and image-heavy landing pages where loading every item immediately would make the page feel too long.

27. Image Gallery with Image Detail Drawer

An image gallery with an image detail drawer lets users inspect extra information without leaving the gallery. This pattern is useful for portfolio case studies, product galleries, real estate images, media libraries, design review tools, travel collections, and visual documentation pages.

This example uses JavaScript drawer state. Clicking a CSS-illustrated gallery item opens a side drawer with the selected image title, category, date, size, tags, and description. The drawer can be closed with the close button, overlay click, or Escape key. No external image files are loaded.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twentyseven]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-twentyseven-grid]");
  const status = gallery.querySelector("[data-vb-gallery-twentyseven-status]");
  const overlay = gallery.querySelector("[data-vb-gallery-twentyseven-overlay]");
  const drawer = gallery.querySelector("[data-vb-gallery-twentyseven-drawer]");
  const closeButton = gallery.querySelector("[data-vb-gallery-twentyseven-close]");
  const drawerArt = gallery.querySelector("[data-vb-gallery-twentyseven-drawer-art]");
  const drawerTitle = gallery.querySelector("[data-vb-gallery-twentyseven-drawer-title]");
  const drawerCategory = gallery.querySelector("[data-vb-gallery-twentyseven-drawer-category]");
  const drawerDescription = gallery.querySelector("[data-vb-gallery-twentyseven-drawer-description]");
  const drawerDate = gallery.querySelector("[data-vb-gallery-twentyseven-drawer-date]");
  const drawerSize = gallery.querySelector("[data-vb-gallery-twentyseven-drawer-size]");
  const drawerTags = gallery.querySelector("[data-vb-gallery-twentyseven-drawer-tags]");

  const items = [
    {
      id: "blue-interface",
      title: "Blue Interface",
      category: "Portfolio",
      art: "vb-gallery-twentyseven-art-blue",
      drawerArt: "vb-gallery-twentyseven-drawer-art-blue",
      date: "Jan 2026",
      size: "1600×900",
      tags: ["portfolio", "ui", "blue"],
      description: "A CSS-only portfolio image concept with a clean interface-inspired visual."
    },
    {
      id: "gold-travel",
      title: "Gold Travel",
      category: "Travel",
      art: "vb-gallery-twentyseven-art-gold",
      drawerArt: "vb-gallery-twentyseven-drawer-art-gold",
      date: "Feb 2026",
      size: "1400×1000",
      tags: ["travel", "warm", "campaign"],
      description: "A warm CSS travel image concept for destination galleries and landing pages."
    },
    {
      id: "purple-product",
      title: "Purple Product",
      category: "Product",
      art: "vb-gallery-twentyseven-art-purple",
      drawerArt: "vb-gallery-twentyseven-drawer-art-purple",
      date: "Mar 2026",
      size: "1200×1200",
      tags: ["product", "launch", "object"],
      description: "A product-style CSS image used for ecommerce galleries and launch sections."
    },
    {
      id: "green-forest",
      title: "Green Forest",
      category: "Nature",
      art: "vb-gallery-twentyseven-art-green",
      drawerArt: "vb-gallery-twentyseven-drawer-art-green",
      date: "Apr 2026",
      size: "1800×1100",
      tags: ["nature", "green", "outdoor"],
      description: "A CSS nature illustration for peaceful galleries, travel pages, and moodboards."
    },
    {
      id: "pink-abstract",
      title: "Pink Abstract",
      category: "Abstract",
      art: "vb-gallery-twentyseven-art-pink",
      drawerArt: "vb-gallery-twentyseven-drawer-art-pink",
      date: "May 2026",
      size: "1500×1500",
      tags: ["abstract", "creative", "poster"],
      description: "A bright abstract CSS image concept for creative inspiration galleries."
    },
    {
      id: "city-night",
      title: "City Night",
      category: "Architecture",
      art: "vb-gallery-twentyseven-art-city",
      drawerArt: "vb-gallery-twentyseven-drawer-art-city",
      date: "Jun 2026",
      size: "1920×1080",
      tags: ["city", "architecture", "night"],
      description: "A CSS city-night image card for real estate, architecture, and urban galleries."
    }
  ];

  function createCard(item, index) {
    return `
      <button type="button" class="vb-gallery-twentyseven-card" data-id="${item.id}" style="animation-delay: ${index * 45}ms">
        <div class="vb-gallery-twentyseven-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-twentyseven-body">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </button>
    `;
  }

  function openDrawer(id) {
    const item = items.find(function (galleryItem) {
      return galleryItem.id === id;
    });

    if (!item) return;

    drawerArt.className = "vb-gallery-twentyseven-drawer-art " + item.drawerArt;
    drawerTitle.textContent = item.title;
    drawerCategory.textContent = item.category;
    drawerDescription.textContent = item.description;
    drawerDate.textContent = item.date;
    drawerSize.textContent = item.size;
    drawerTags.innerHTML = item.tags.map(function (tag) {
      return "<small>" + tag + "</small>";
    }).join("");

    status.textContent = "Viewing: " + item.title;
    overlay.hidden = false;
    drawer.hidden = false;
    document.body.style.overflow = "hidden";
  }

  function closeDrawer() {
    overlay.hidden = true;
    drawer.hidden = true;
    status.textContent = "Select an image";
    document.body.style.overflow = "";
  }

  grid.addEventListener("click", function (event) {
    const card = event.target.closest("[data-id]");
    if (!card) return;

    openDrawer(card.getAttribute("data-id"));
  });

  closeButton.addEventListener("click", closeDrawer);
  overlay.addEventListener("click", closeDrawer);

  document.addEventListener("keydown", function (event) {
    if (event.key === "Escape" && !drawer.hidden) {
      closeDrawer();
    }
  });

  grid.innerHTML = items.map(createCard).join("");
})();

HTML

<div class="vb-gallery-twentyseven-demo">
  <div class="vb-gallery-twentyseven-shell" data-vb-gallery-twentyseven>
    <div class="vb-gallery-twentyseven-head">
      <div>
        <span>Example 27</span>
        <h3>Image Gallery with Image Detail Drawer</h3>
        <p>Click a CSS-illustrated gallery card and open a JavaScript-powered detail drawer.</p>
      </div>

      <strong data-vb-gallery-twentyseven-status>Select an image</strong>
    </div>

    <div class="vb-gallery-twentyseven-grid" data-vb-gallery-twentyseven-grid></div>

    <div class="vb-gallery-twentyseven-overlay" data-vb-gallery-twentyseven-overlay hidden></div>

    <aside class="vb-gallery-twentyseven-drawer" data-vb-gallery-twentyseven-drawer hidden>
      <button type="button" class="vb-gallery-twentyseven-close" data-vb-gallery-twentyseven-close>Close</button>

      <div class="vb-gallery-twentyseven-drawer-art" data-vb-gallery-twentyseven-drawer-art></div>

      <div class="vb-gallery-twentyseven-drawer-content">
        <span data-vb-gallery-twentyseven-drawer-category>Category</span>
        <h4 data-vb-gallery-twentyseven-drawer-title>Gallery Item</h4>
        <p data-vb-gallery-twentyseven-drawer-description>Selected image description.</p>

        <div class="vb-gallery-twentyseven-meta">
          <div>
            <small>Date</small>
            <strong data-vb-gallery-twentyseven-drawer-date>2026</strong>
          </div>

          <div>
            <small>Size</small>
            <strong data-vb-gallery-twentyseven-drawer-size>1600×900</strong>
          </div>
        </div>

        <div class="vb-gallery-twentyseven-tags" data-vb-gallery-twentyseven-drawer-tags></div>
      </div>
    </aside>
  </div>
</div>

CSS

.vb-gallery-twentyseven-demo,
.vb-gallery-twentyseven-demo * {
  box-sizing: border-box;
}

.vb-gallery-twentyseven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 14% 14%, rgba(37, 99, 235, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(16, 185, 129, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfdf5 52%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.20);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-gallery-twentyseven-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-twentyseven-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 24px;
}

.vb-gallery-twentyseven-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-twentyseven-head h3 {
  max-width: 820px;
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-twentyseven-head p {
  max-width: 740px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-twentyseven-head strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-twentyseven-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-gallery-twentyseven-card {
  overflow: hidden;
  padding: 0;
  border: 0;
  border-radius: 30px;
  background: #ffffff;
  cursor: pointer;
  text-align: left;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  transition: transform 0.22s ease, box-shadow 0.22s ease;
  animation: vbGalleryTwentysevenIn 0.34s ease both;
}

@keyframes vbGalleryTwentysevenIn {
  from {
    opacity: 0;
    transform: translateY(16px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-twentyseven-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 26px 64px rgba(15, 23, 42, 0.16);
}

.vb-gallery-twentyseven-art,
.vb-gallery-twentyseven-drawer-art {
  position: relative;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-twentyseven-art {
  height: 245px;
}

.vb-gallery-twentyseven-drawer-art {
  height: 320px;
}

.vb-gallery-twentyseven-art::before,
.vb-gallery-twentyseven-art::after,
.vb-gallery-twentyseven-drawer-art::before,
.vb-gallery-twentyseven-drawer-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-twentyseven-art-blue,
.vb-gallery-twentyseven-drawer-art-blue {
  background: linear-gradient(135deg, #020617 0%, #2563eb 54%, #38bdf8 100%);
}

.vb-gallery-twentyseven-art-blue::before,
.vb-gallery-twentyseven-drawer-art-blue::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 60%;
  border-radius: 28px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 20px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-twentyseven-art-blue::after,
.vb-gallery-twentyseven-drawer-art-blue::after {
  right: 16%;
  bottom: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-twentyseven-art-gold,
.vb-gallery-twentyseven-drawer-art-gold {
  background: linear-gradient(180deg, #fef3c7 0%, #f59e0b 52%, #78350f 100%);
}

.vb-gallery-twentyseven-art-gold::before,
.vb-gallery-twentyseven-drawer-art-gold::before {
  left: -8%;
  right: -8%;
  bottom: -18%;
  height: 56%;
  background: #451a03;
  clip-path: polygon(0 100%, 0 58%, 15% 42%, 30% 66%, 48% 34%, 66% 62%, 83% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-twentyseven-art-gold::after,
.vb-gallery-twentyseven-drawer-art-gold::after {
  right: 18%;
  top: 16%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #fffbeb;
  box-shadow: 0 0 64px rgba(255,251,235,0.72);
}

.vb-gallery-twentyseven-art-purple,
.vb-gallery-twentyseven-drawer-art-purple {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #ddd6fe 36%, #581c87 100%);
}

.vb-gallery-twentyseven-art-purple::before,
.vb-gallery-twentyseven-drawer-art-purple::before {
  left: 50%;
  top: 50%;
  width: 132px;
  height: 132px;
  border-radius: 30px;
  background: linear-gradient(135deg, #ffffff, #c4b5fd);
  box-shadow: 0 30px 80px rgba(88,28,135,0.32);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentyseven-art-purple::after,
.vb-gallery-twentyseven-drawer-art-purple::after {
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  border-radius: 20px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentyseven-art-green,
.vb-gallery-twentyseven-drawer-art-green {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-twentyseven-art-green::before,
.vb-gallery-twentyseven-drawer-art-green::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-twentyseven-art-green::after,
.vb-gallery-twentyseven-drawer-art-green::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-twentyseven-art-pink,
.vb-gallery-twentyseven-drawer-art-pink {
  background: linear-gradient(135deg, #500724 0%, #be123c 52%, #fb7185 100%);
}

.vb-gallery-twentyseven-art-pink::before,
.vb-gallery-twentyseven-drawer-art-pink::before {
  left: 50%;
  top: 52%;
  width: 140px;
  height: 140px;
  border-radius: 42% 58% 62% 38%;
  background: radial-gradient(circle at 35% 30%, #fff1f2, #fb7185 44%, #9f1239 100%);
  transform: translate(-50%, -50%) rotate(22deg);
  box-shadow: 0 26px 70px rgba(80,7,36,0.36);
}

.vb-gallery-twentyseven-art-pink::after,
.vb-gallery-twentyseven-drawer-art-pink::after {
  left: 14%;
  top: 16%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: rgba(255,255,255,0.22);
  filter: blur(6px);
}

.vb-gallery-twentyseven-art-city,
.vb-gallery-twentyseven-drawer-art-city {
  background: linear-gradient(180deg, #312e81 0%, #111827 66%, #020617 100%);
}

.vb-gallery-twentyseven-art-city::before,
.vb-gallery-twentyseven-drawer-art-city::before {
  left: 10%;
  bottom: 0;
  width: 18%;
  height: 58%;
  background: #020617;
  box-shadow:
    70px -42px 0 #020617,
    142px -12px 0 #020617,
    224px -62px 0 #020617,
    304px -24px 0 #020617;
}

.vb-gallery-twentyseven-art-city::after,
.vb-gallery-twentyseven-drawer-art-city::after {
  inset: 18% 10% auto 10%;
  height: 3px;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,0.82) 0 8px, transparent 8px 24px);
  box-shadow:
    0 34px 0 rgba(255,255,255,0.42),
    0 70px 0 rgba(255,255,255,0.22);
}

.vb-gallery-twentyseven-body {
  display: grid;
  gap: 8px;
  padding: 18px;
}

.vb-gallery-twentyseven-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #eff6ff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-twentyseven-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 22px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-twentyseven-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-twentyseven-overlay {
  position: fixed;
  inset: 0;
  z-index: 99998;
  background: rgba(15, 23, 42, 0.54);
  backdrop-filter: blur(6px);
}

.vb-gallery-twentyseven-overlay[hidden] {
  display: none;
}

.vb-gallery-twentyseven-drawer {
  position: fixed;
  z-index: 99999;
  top: 18px;
  right: 18px;
  bottom: 18px;
  width: min(460px, calc(100vw - 36px));
  overflow: auto;
  border-radius: 34px;
  background: #ffffff;
  box-shadow: 0 34px 120px rgba(15, 23, 42, 0.34);
  animation: vbGalleryTwentysevenDrawer 0.28s ease both;
}

.vb-gallery-twentyseven-drawer[hidden] {
  display: none;
}

@keyframes vbGalleryTwentysevenDrawer {
  from {
    opacity: 0;
    transform: translateX(40px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateX(0) scale(1);
  }
}

.vb-gallery-twentyseven-close {
  position: sticky;
  top: 14px;
  z-index: 10;
  float: right;
  margin: 14px 14px -58px 0;
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.78);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 10px 14px;
  font-size: 12px;
  font-weight: 950;
  cursor: pointer;
  backdrop-filter: blur(10px);
}

.vb-gallery-twentyseven-drawer-content {
  display: grid;
  gap: 14px;
  padding: 22px;
}

.vb-gallery-twentyseven-drawer-content > span {
  display: inline-flex;
  width: fit-content;
  padding: 7px 10px;
  border-radius: 999px;
  background: #eff6ff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-gallery-twentyseven-drawer-content h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(30px, 4vw, 48px) !important;
  line-height: 1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-gallery-twentyseven-drawer-content p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-gallery-twentyseven-meta {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.vb-gallery-twentyseven-meta div {
  padding: 14px;
  border-radius: 20px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-gallery-twentyseven-meta small {
  display: block;
  margin-bottom: 5px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 11px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-gallery-twentyseven-meta strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 950;
}

.vb-gallery-twentyseven-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.vb-gallery-twentyseven-tags small {
  display: inline-flex;
  padding: 6px 9px;
  border-radius: 999px;
  background: #ecfdf5;
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
  font-size: 11px;
  font-weight: 950;
}

@media (max-width: 980px) {
  .vb-gallery-twentyseven-head {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-twentyseven-head strong {
    justify-content: center;
    width: 100%;
  }

  .vb-gallery-twentyseven-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 620px) {
  .vb-gallery-twentyseven-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-twentyseven-head h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-twentyseven-grid {
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentyseven-drawer {
    inset: auto 10px 10px 10px;
    width: auto;
    max-height: calc(100vh - 20px);
    border-radius: 26px;
  }

  .vb-gallery-twentyseven-drawer-art {
    height: 240px;
  }
}

This image gallery with an image detail drawer is useful for portfolio case studies, ecommerce product galleries, media libraries, real estate photo collections, visual documentation, and image-heavy pages where users need more context without opening a separate page.

28. Image Gallery with Select and Bulk Actions

An image gallery with select and bulk actions works well when users need to manage several images at once. This pattern is useful for media libraries, admin dashboards, product image managers, portfolio review tools, client approval galleries, and visual asset management interfaces.

This example uses JavaScript selection state. Users can select multiple CSS-illustrated gallery cards, select all, clear selection, and apply a simulated bulk archive action. The selected count, action message, and selected card styles update instantly.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twentyeight]");
  if (!gallery) return;

  const grid = gallery.querySelector("[data-vb-gallery-twentyeight-grid]");
  const count = gallery.querySelector("[data-vb-gallery-twentyeight-count]");
  const message = gallery.querySelector("[data-vb-gallery-twentyeight-message]");
  const selectAllButton = gallery.querySelector("[data-vb-gallery-twentyeight-select-all]");
  const clearButton = gallery.querySelector("[data-vb-gallery-twentyeight-clear]");
  const archiveButton = gallery.querySelector("[data-vb-gallery-twentyeight-archive]");

  const selectedIds = new Set();
  const archivedIds = new Set();

  const items = [
    {
      id: "bulk-blue",
      title: "Blue Portfolio",
      category: "Portfolio",
      art: "vb-gallery-twentyeight-art-blue",
      description: "A CSS-only portfolio image for selectable gallery workflows."
    },
    {
      id: "bulk-gold",
      title: "Gold Travel",
      category: "Travel",
      art: "vb-gallery-twentyeight-art-gold",
      description: "A warm CSS travel card that can be selected for bulk actions."
    },
    {
      id: "bulk-purple",
      title: "Purple Product",
      category: "Product",
      art: "vb-gallery-twentyeight-art-purple",
      description: "A CSS product visual used in multi-select gallery management."
    },
    {
      id: "bulk-green",
      title: "Green Nature",
      category: "Nature",
      art: "vb-gallery-twentyeight-art-green",
      description: "A CSS nature image that can be selected and archived."
    },
    {
      id: "bulk-pink",
      title: "Pink Abstract",
      category: "Abstract",
      art: "vb-gallery-twentyeight-art-pink",
      description: "A creative CSS gallery card for selection state examples."
    },
    {
      id: "bulk-city",
      title: "City Night",
      category: "Architecture",
      art: "vb-gallery-twentyeight-art-city",
      description: "A CSS city image for admin-style image gallery actions."
    },
    {
      id: "bulk-cyan",
      title: "Cyan Glass",
      category: "Design",
      art: "vb-gallery-twentyeight-art-cyan",
      description: "A bright CSS image card that supports selected and archived states."
    },
    {
      id: "bulk-red",
      title: "Red Motion",
      category: "Event",
      art: "vb-gallery-twentyeight-art-red",
      description: "A CSS event-style image used for gallery bulk action testing."
    }
  ];

  function createCard(item, index) {
    const selectedClass = selectedIds.has(item.id) ? "is-selected" : "";
    const archivedClass = archivedIds.has(item.id) ? "is-archived" : "";
    const checkText = selectedIds.has(item.id) ? "✓" : "+";

    return `
      <button type="button" class="vb-gallery-twentyeight-card ${selectedClass} ${archivedClass}" data-id="${item.id}" style="animation-delay: ${index * 35}ms">
        <span class="vb-gallery-twentyeight-check">${checkText}</span>
        <div class="vb-gallery-twentyeight-art ${item.art}" aria-label="${item.title} illustration" role="img"></div>

        <div class="vb-gallery-twentyeight-body">
          <span>${item.category}</span>
          <h4>${item.title}</h4>
          <p>${item.description}</p>
        </div>
      </button>
    `;
  }

  function updateCount() {
    count.textContent = selectedIds.size === 1 ? "1 selected" : selectedIds.size + " selected";
  }

  function updateMessage(text) {
    if (text) {
      message.textContent = text;
      return;
    }

    if (selectedIds.size === 0) {
      message.textContent = "Select gallery images to enable a bulk action.";
    } else {
      message.textContent = "Ready to run a bulk action on " + selectedIds.size + " selected image" + (selectedIds.size === 1 ? "." : "s.");
    }
  }

  function renderGallery(messageText) {
    grid.innerHTML = items.map(createCard).join("");
    updateCount();
    updateMessage(messageText);
  }

  function toggleSelected(id) {
    if (archivedIds.has(id)) return;

    if (selectedIds.has(id)) {
      selectedIds.delete(id);
    } else {
      selectedIds.add(id);
    }

    renderGallery();
  }

  grid.addEventListener("click", function (event) {
    const card = event.target.closest("[data-id]");
    if (!card) return;

    toggleSelected(card.getAttribute("data-id"));
  });

  selectAllButton.addEventListener("click", function () {
    items.forEach(function (item) {
      if (!archivedIds.has(item.id)) {
        selectedIds.add(item.id);
      }
    });

    renderGallery("All active gallery images selected.");
  });

  clearButton.addEventListener("click", function () {
    selectedIds.clear();
    renderGallery("Selection cleared.");
  });

  archiveButton.addEventListener("click", function () {
    if (selectedIds.size === 0) {
      renderGallery("Select at least one image before running a bulk action.");
      return;
    }

    selectedIds.forEach(function (id) {
      archivedIds.add(id);
    });

    const archivedCount = selectedIds.size;
    selectedIds.clear();
    renderGallery(archivedCount + " image" + (archivedCount === 1 ? " was" : "s were") + " archived in JavaScript state.");
  });

  renderGallery();
})();

HTML

<div class="vb-gallery-twentyeight-demo">
  <div class="vb-gallery-twentyeight-shell" data-vb-gallery-twentyeight>
    <div class="vb-gallery-twentyeight-top">
      <div>
        <span>Example 28</span>
        <h3>Image Gallery with Select and Bulk Actions</h3>
        <p>Select multiple CSS-illustrated gallery cards and run JavaScript bulk actions.</p>
      </div>

      <strong data-vb-gallery-twentyeight-count>0 selected</strong>
    </div>

    <div class="vb-gallery-twentyeight-toolbar">
      <button type="button" data-vb-gallery-twentyeight-select-all>Select All</button>
      <button type="button" data-vb-gallery-twentyeight-clear>Clear Selection</button>
      <button type="button" data-vb-gallery-twentyeight-archive>Archive Selected</button>
    </div>

    <div class="vb-gallery-twentyeight-message" data-vb-gallery-twentyeight-message>Select gallery images to enable a bulk action.</div>

    <div class="vb-gallery-twentyeight-grid" data-vb-gallery-twentyeight-grid></div>
  </div>
</div>

CSS

.vb-gallery-twentyeight-demo,
.vb-gallery-twentyeight-demo * {
  box-sizing: border-box;
}

.vb-gallery-twentyeight-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 14% 14%, rgba(245, 158, 11, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(239, 68, 68, 0.14), transparent 34%),
    linear-gradient(135deg, #fffbeb 0%, #fff1f2 52%, #ffffff 100%) !important;
  border: 1px solid rgba(245, 158, 11, 0.22);
  box-shadow: 0 28px 80px rgba(146, 64, 14, 0.10);
}

.vb-gallery-twentyeight-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-twentyeight-top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 20px;
}

.vb-gallery-twentyeight-top span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-twentyeight-top h3 {
  max-width: 850px;
  margin: 0 0 14px !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-twentyeight-top p {
  max-width: 750px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-twentyeight-top strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #111827;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-twentyeight-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 12px;
  padding: 12px;
  border-radius: 24px;
  background: rgba(255,255,255,0.78);
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.08);
}

.vb-gallery-twentyeight-toolbar button {
  appearance: none;
  border: 1px solid rgba(245, 158, 11, 0.24);
  border-radius: 999px;
  background: #ffffff;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  padding: 11px 15px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.vb-gallery-twentyeight-toolbar button:hover {
  transform: translateY(-2px);
  border-color: rgba(245, 158, 11, 0.62);
}

.vb-gallery-twentyeight-toolbar button:last-child {
  border-color: transparent;
  background: linear-gradient(135deg, #f59e0b, #ef4444);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  box-shadow: 0 14px 34px rgba(245, 158, 11, 0.22);
}

.vb-gallery-twentyeight-message {
  margin-bottom: 18px;
  padding: 12px 14px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.20);
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 13px;
  line-height: 1.5;
  font-weight: 850;
}

.vb-gallery-twentyeight-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.vb-gallery-twentyeight-card {
  position: relative;
  overflow: hidden;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 28px;
  background: #ffffff;
  cursor: pointer;
  text-align: left;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease, opacity 0.22s ease;
  animation: vbGalleryTwentyeightIn 0.34s ease both;
}

@keyframes vbGalleryTwentyeightIn {
  from {
    opacity: 0;
    transform: translateY(16px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.vb-gallery-twentyeight-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 26px 64px rgba(15, 23, 42, 0.15);
}

.vb-gallery-twentyeight-card.is-selected {
  border-color: rgba(245, 158, 11, 0.92);
  box-shadow: 0 24px 70px rgba(245, 158, 11, 0.24);
}

.vb-gallery-twentyeight-card.is-archived {
  opacity: 0.42;
  filter: grayscale(1);
}

.vb-gallery-twentyeight-check {
  position: absolute;
  z-index: 6;
  top: 13px;
  right: 13px;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.62);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  font-weight: 950;
  backdrop-filter: blur(10px);
}

.vb-gallery-twentyeight-card.is-selected .vb-gallery-twentyeight-check {
  background: linear-gradient(135deg, #f59e0b, #ef4444);
}

.vb-gallery-twentyeight-art {
  position: relative;
  height: 225px;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-twentyeight-art::before,
.vb-gallery-twentyeight-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-twentyeight-art-blue {
  background: linear-gradient(135deg, #020617 0%, #2563eb 54%, #38bdf8 100%);
}

.vb-gallery-twentyeight-art-blue::before {
  left: 14%;
  top: 18%;
  width: 72%;
  height: 60%;
  border-radius: 28px;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 20px),
    rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-gallery-twentyeight-art-blue::after {
  right: 16%;
  bottom: 18%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: rgba(255,255,255,0.26);
  filter: blur(8px);
}

.vb-gallery-twentyeight-art-gold {
  background: linear-gradient(180deg, #fef3c7 0%, #f59e0b 52%, #78350f 100%);
}

.vb-gallery-twentyeight-art-gold::before {
  left: -8%;
  right: -8%;
  bottom: -18%;
  height: 56%;
  background: #451a03;
  clip-path: polygon(0 100%, 0 58%, 15% 42%, 30% 66%, 48% 34%, 66% 62%, 83% 40%, 100% 58%, 100% 100%);
}

.vb-gallery-twentyeight-art-gold::after {
  right: 18%;
  top: 16%;
  width: 82px;
  height: 82px;
  border-radius: 999px;
  background: #fffbeb;
  box-shadow: 0 0 64px rgba(255,251,235,0.72);
}

.vb-gallery-twentyeight-art-purple {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #ddd6fe 36%, #581c87 100%);
}

.vb-gallery-twentyeight-art-purple::before {
  left: 50%;
  top: 50%;
  width: 132px;
  height: 132px;
  border-radius: 30px;
  background: linear-gradient(135deg, #ffffff, #c4b5fd);
  box-shadow: 0 30px 80px rgba(88,28,135,0.32);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentyeight-art-purple::after {
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  border-radius: 20px;
  border: 4px solid rgba(124,58,237,0.46);
  transform: translate(-50%, -50%) rotate(14deg);
}

.vb-gallery-twentyeight-art-green {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-twentyeight-art-green::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-twentyeight-art-green::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-twentyeight-art-pink {
  background: linear-gradient(135deg, #500724 0%, #be123c 52%, #fb7185 100%);
}

.vb-gallery-twentyeight-art-pink::before {
  left: 50%;
  top: 52%;
  width: 140px;
  height: 140px;
  border-radius: 42% 58% 62% 38%;
  background: radial-gradient(circle at 35% 30%, #fff1f2, #fb7185 44%, #9f1239 100%);
  transform: translate(-50%, -50%) rotate(22deg);
  box-shadow: 0 26px 70px rgba(80,7,36,0.36);
}

.vb-gallery-twentyeight-art-pink::after {
  left: 14%;
  top: 16%;
  width: 74px;
  height: 74px;
  border-radius: 999px;
  background: rgba(255,255,255,0.22);
  filter: blur(6px);
}

.vb-gallery-twentyeight-art-city {
  background: linear-gradient(180deg, #312e81 0%, #111827 66%, #020617 100%);
}

.vb-gallery-twentyeight-art-city::before {
  left: 10%;
  bottom: 0;
  width: 18%;
  height: 58%;
  background: #020617;
  box-shadow:
    70px -42px 0 #020617,
    142px -12px 0 #020617,
    224px -62px 0 #020617,
    304px -24px 0 #020617;
}

.vb-gallery-twentyeight-art-city::after {
  inset: 18% 10% auto 10%;
  height: 3px;
  background: repeating-linear-gradient(90deg, rgba(255,255,255,0.82) 0 8px, transparent 8px 24px);
  box-shadow:
    0 34px 0 rgba(255,255,255,0.42),
    0 70px 0 rgba(255,255,255,0.22);
}

.vb-gallery-twentyeight-art-cyan {
  background: linear-gradient(135deg, #ecfeff 0%, #06b6d4 48%, #0f766e 100%);
}

.vb-gallery-twentyeight-art-cyan::before {
  left: 16%;
  top: 18%;
  width: 68%;
  height: 62%;
  border-radius: 28px;
  background: rgba(255,255,255,0.20);
  border: 1px solid rgba(255,255,255,0.34);
  backdrop-filter: blur(4px);
}

.vb-gallery-twentyeight-art-cyan::after {
  left: 50%;
  top: 50%;
  width: 92px;
  height: 92px;
  border-radius: 26px;
  background: rgba(255,255,255,0.70);
  transform: translate(-50%, -50%) rotate(20deg);
}

.vb-gallery-twentyeight-art-red {
  background: radial-gradient(circle at 50% 50%, #ffffff 0%, #fecaca 32%, #991b1b 100%);
}

.vb-gallery-twentyeight-art-red::before {
  left: 50%;
  top: 50%;
  width: 150px;
  height: 150px;
  border-radius: 999px;
  border: 18px solid rgba(255,255,255,0.36);
  border-left-color: rgba(255,255,255,0.86);
  transform: translate(-50%, -50%) rotate(18deg);
}

.vb-gallery-twentyeight-art-red::after {
  left: 50%;
  top: 50%;
  width: 76px;
  height: 76px;
  border-radius: 999px;
  background: rgba(255,255,255,0.76);
  transform: translate(-50%, -50%);
}

.vb-gallery-twentyeight-body {
  display: grid;
  gap: 8px;
  padding: 17px;
}

.vb-gallery-twentyeight-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #fffbeb;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-twentyeight-body h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-twentyeight-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

@media (max-width: 1050px) {
  .vb-gallery-twentyeight-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 820px) {
  .vb-gallery-twentyeight-top {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-twentyeight-top strong {
    justify-content: center;
    width: 100%;
  }

  .vb-gallery-twentyeight-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .vb-gallery-twentyeight-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-twentyeight-top h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-twentyeight-toolbar {
    display: grid;
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentyeight-grid {
    grid-template-columns: 1fr;
  }
}

This image gallery with select and bulk actions is useful for media libraries, product image managers, admin dashboards, client approval galleries, asset review tools, and any image management interface where users need to handle several gallery items at once.

29. Image Gallery with Zoom and Pan Viewer

An image gallery with zoom and pan viewer lets users inspect visual details inside a larger preview window. This JavaScript gallery pattern is useful for product images, portfolio previews, map-style visuals, technical screenshots, artwork galleries, real estate images, and media viewers where users need more control than a normal lightbox.

This example uses JavaScript zoom and pan state. Users can open a CSS-generated gallery image, zoom in, zoom out, reset the view, drag the image inside the viewer, use mouse wheel zoom, and close the viewer with the Escape key. The demo uses CSS-only illustrated images, so it does not load any external image files.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-twentynine]");
  if (!gallery) return;

  const status = gallery.querySelector("[data-vb-gallery-twentynine-status]");
  const viewer = gallery.querySelector("[data-vb-gallery-twentynine-viewer]");
  const large = gallery.querySelector("[data-vb-gallery-twentynine-large]");
  const canvas = gallery.querySelector("[data-vb-gallery-twentynine-canvas]");
  const title = gallery.querySelector("[data-vb-gallery-twentynine-title]");
  const category = gallery.querySelector("[data-vb-gallery-twentynine-category]");
  const hint = gallery.querySelector("[data-vb-gallery-twentynine-hint]");
  const zoomIn = gallery.querySelector("[data-vb-gallery-twentynine-zoom-in]");
  const zoomOut = gallery.querySelector("[data-vb-gallery-twentynine-zoom-out]");
  const reset = gallery.querySelector("[data-vb-gallery-twentynine-reset]");

  let scale = 1;
  let translateX = 0;
  let translateY = 0;
  let isDragging = false;
  let startX = 0;
  let startY = 0;

  function updateTransform() {
    large.style.transform = "translate(" + translateX + "px, " + translateY + "px) scale(" + scale + ")";
    hint.textContent = "Zoom: " + Math.round(scale * 100) + "% · Drag inside the preview to pan";
  }

  function resetView() {
    scale = 1;
    translateX = 0;
    translateY = 0;
    updateTransform();
  }

  function openViewer(card) {
    const art = card.getAttribute("data-art");
    const selectedTitle = card.getAttribute("data-title");
    const selectedCategory = card.getAttribute("data-category");

    large.className = "vb-gallery-twentynine-large vb-gallery-twentynine-large-" + art;
    title.textContent = selectedTitle;
    category.textContent = selectedCategory;
    status.textContent = "Viewing: " + selectedTitle;
    viewer.hidden = false;
    document.body.style.overflow = "hidden";
    resetView();
  }

  function closeViewer() {
    viewer.hidden = true;
    document.body.style.overflow = "";
    status.textContent = "Click an image";
  }

  function changeZoom(amount) {
    scale = Math.min(3, Math.max(0.75, scale + amount));

    if (scale === 1) {
      translateX = 0;
      translateY = 0;
    }

    updateTransform();
  }

  gallery.addEventListener("click", function (event) {
    const card = event.target.closest(".vb-gallery-twentynine-card");
    const close = event.target.closest("[data-vb-gallery-twentynine-close]");

    if (card) {
      openViewer(card);
    }

    if (close) {
      closeViewer();
    }
  });

  zoomIn.addEventListener("click", function () {
    changeZoom(0.25);
  });

  zoomOut.addEventListener("click", function () {
    changeZoom(-0.25);
  });

  reset.addEventListener("click", resetView);

  canvas.addEventListener("wheel", function (event) {
    event.preventDefault();
    changeZoom(event.deltaY < 0 ? 0.15 : -0.15);
  });

  large.addEventListener("pointerdown", function (event) {
    isDragging = true;
    startX = event.clientX - translateX;
    startY = event.clientY - translateY;
    large.classList.add("is-dragging");
    large.setPointerCapture(event.pointerId);
  });

  large.addEventListener("pointermove", function (event) {
    if (!isDragging) return;

    translateX = event.clientX - startX;
    translateY = event.clientY - startY;
    updateTransform();
  });

  large.addEventListener("pointerup", function () {
    isDragging = false;
    large.classList.remove("is-dragging");
  });

  document.addEventListener("keydown", function (event) {
    if (event.key === "Escape" && !viewer.hidden) {
      closeViewer();
    }
  });

  updateTransform();
})();

HTML

<div class="vb-gallery-twentynine-demo">
  <div class="vb-gallery-twentynine-shell" data-vb-gallery-twentynine>
    <div class="vb-gallery-twentynine-head">
      <div>
        <span>Example 29</span>
        <h3>Image Gallery with Zoom and Pan Viewer</h3>
        <p>Open a CSS-generated gallery image, zoom into it, and drag the preview around with JavaScript.</p>
      </div>

      <strong data-vb-gallery-twentynine-status>Click an image</strong>
    </div>

    <div class="vb-gallery-twentynine-grid">
      <button type="button" class="vb-gallery-twentynine-card" data-title="Circuit Detail" data-category="Technical" data-art="circuit">
        <div class="vb-gallery-twentynine-art vb-gallery-twentynine-art-circuit" aria-label="Circuit Detail illustration" role="img"></div>
        <div class="vb-gallery-twentynine-body">
          <span>Technical</span>
          <h4>Circuit Detail</h4>
          <p>Open and zoom into a technical CSS illustration.</p>
        </div>
      </button>

      <button type="button" class="vb-gallery-twentynine-card" data-title="Product Closeup" data-category="Product" data-art="product">
        <div class="vb-gallery-twentynine-art vb-gallery-twentynine-art-product" aria-label="Product Closeup illustration" role="img"></div>
        <div class="vb-gallery-twentynine-body">
          <span>Product</span>
          <h4>Product Closeup</h4>
          <p>Inspect a product-style CSS card in the viewer.</p>
        </div>
      </button>

      <button type="button" class="vb-gallery-twentynine-card" data-title="Map Pattern" data-category="Map UI" data-art="map">
        <div class="vb-gallery-twentynine-art vb-gallery-twentynine-art-map" aria-label="Map Pattern illustration" role="img"></div>
        <div class="vb-gallery-twentynine-body">
          <span>Map UI</span>
          <h4>Map Pattern</h4>
          <p>Pan around a map-style CSS visual after zooming.</p>
        </div>
      </button>
    </div>

    <div class="vb-gallery-twentynine-viewer" data-vb-gallery-twentynine-viewer hidden>
      <div class="vb-gallery-twentynine-backdrop" data-vb-gallery-twentynine-close></div>

      <div class="vb-gallery-twentynine-modal">
        <div class="vb-gallery-twentynine-toolbar">
          <div>
            <span data-vb-gallery-twentynine-category>Category</span>
            <strong data-vb-gallery-twentynine-title>Gallery Image</strong>
          </div>

          <div class="vb-gallery-twentynine-tools">
            <button type="button" data-vb-gallery-twentynine-zoom-out>−</button>
            <button type="button" data-vb-gallery-twentynine-reset>Reset</button>
            <button type="button" data-vb-gallery-twentynine-zoom-in>+</button>
            <button type="button" data-vb-gallery-twentynine-close>Close</button>
          </div>
        </div>

        <div class="vb-gallery-twentynine-canvas" data-vb-gallery-twentynine-canvas>
          <div class="vb-gallery-twentynine-large" data-vb-gallery-twentynine-large></div>
        </div>

        <div class="vb-gallery-twentynine-hint" data-vb-gallery-twentynine-hint>Zoom: 100% · Drag inside the preview to pan</div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-gallery-twentynine-demo,
.vb-gallery-twentynine-demo * {
  box-sizing: border-box;
}

.vb-gallery-twentynine-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 14% 14%, rgba(34, 197, 94, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(59, 130, 246, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #eff6ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.22);
  box-shadow: 0 28px 80px rgba(21, 128, 61, 0.10);
}

.vb-gallery-twentynine-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-twentynine-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 24px;
}

.vb-gallery-twentynine-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-twentynine-head h3 {
  max-width: 840px;
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-twentynine-head p {
  max-width: 760px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-twentynine-head strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-twentynine-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-gallery-twentynine-card {
  overflow: hidden;
  padding: 0;
  border: 0;
  border-radius: 30px;
  background: #ffffff;
  text-align: left;
  cursor: zoom-in;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  transition: transform 0.22s ease, box-shadow 0.22s ease;
}

.vb-gallery-twentynine-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 26px 64px rgba(15, 23, 42, 0.16);
}

.vb-gallery-twentynine-art,
.vb-gallery-twentynine-large {
  position: relative;
  overflow: hidden;
  background: #111827;
}

.vb-gallery-twentynine-art {
  height: 270px;
}

.vb-gallery-twentynine-large {
  width: 780px;
  height: 520px;
  max-width: none;
  transform-origin: center center;
  cursor: grab;
  user-select: none;
}

.vb-gallery-twentynine-large.is-dragging {
  cursor: grabbing;
}

.vb-gallery-twentynine-art::before,
.vb-gallery-twentynine-art::after,
.vb-gallery-twentynine-large::before,
.vb-gallery-twentynine-large::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-twentynine-art-circuit,
.vb-gallery-twentynine-large-circuit {
  background:
    linear-gradient(90deg, rgba(34,197,94,0.18) 1px, transparent 1px),
    linear-gradient(0deg, rgba(34,197,94,0.18) 1px, transparent 1px),
    radial-gradient(circle at 25% 30%, rgba(134,239,172,0.72), transparent 12%),
    radial-gradient(circle at 74% 68%, rgba(59,130,246,0.52), transparent 14%),
    linear-gradient(135deg, #020617, #064e3b 54%, #0f172a);
  background-size: 34px 34px, 34px 34px, auto, auto, auto;
}

.vb-gallery-twentynine-art-circuit::before,
.vb-gallery-twentynine-large-circuit::before {
  left: 12%;
  top: 20%;
  width: 76%;
  height: 56%;
  border-radius: 26px;
  border: 2px solid rgba(134,239,172,0.42);
  background:
    linear-gradient(90deg, transparent 0 28%, rgba(134,239,172,0.70) 28% 30%, transparent 30% 100%),
    linear-gradient(0deg, transparent 0 48%, rgba(96,165,250,0.65) 48% 50%, transparent 50% 100%);
}

.vb-gallery-twentynine-art-circuit::after,
.vb-gallery-twentynine-large-circuit::after {
  left: 22%;
  top: 30%;
  width: 42px;
  height: 42px;
  border-radius: 999px;
  background: #86efac;
  box-shadow:
    160px 120px 0 #60a5fa,
    340px 34px 0 #86efac,
    420px 160px 0 #22c55e;
}

.vb-gallery-twentynine-art-product,
.vb-gallery-twentynine-large-product {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #e0e7ff 34%, #312e81 100%);
}

.vb-gallery-twentynine-art-product::before,
.vb-gallery-twentynine-large-product::before {
  left: 50%;
  top: 50%;
  width: 170px;
  height: 170px;
  border-radius: 34px;
  background: linear-gradient(135deg, #ffffff, #c7d2fe);
  box-shadow: 0 30px 80px rgba(49,46,129,0.34);
  transform: translate(-50%, -50%) rotate(18deg);
}

.vb-gallery-twentynine-art-product::after,
.vb-gallery-twentynine-large-product::after {
  left: 50%;
  top: 50%;
  width: 92px;
  height: 92px;
  border-radius: 24px;
  border: 5px solid rgba(79,70,229,0.50);
  transform: translate(-50%, -50%) rotate(18deg);
}

.vb-gallery-twentynine-art-map,
.vb-gallery-twentynine-large-map {
  background:
    radial-gradient(circle at 24% 34%, #fef3c7 0 7%, transparent 8%),
    radial-gradient(circle at 64% 66%, #bfdbfe 0 8%, transparent 9%),
    linear-gradient(135deg, #f8fafc, #dbeafe 46%, #bbf7d0);
}

.vb-gallery-twentynine-art-map::before,
.vb-gallery-twentynine-large-map::before {
  inset: 13%;
  border-radius: 34px;
  background:
    linear-gradient(48deg, transparent 0 46%, rgba(15,23,42,0.16) 46% 48%, transparent 48% 100%),
    linear-gradient(-28deg, transparent 0 38%, rgba(15,23,42,0.12) 38% 40%, transparent 40% 100%),
    linear-gradient(90deg, transparent 0 58%, rgba(15,23,42,0.10) 58% 60%, transparent 60% 100%);
  border: 2px solid rgba(15,23,42,0.10);
}

.vb-gallery-twentynine-art-map::after,
.vb-gallery-twentynine-large-map::after {
  left: 42%;
  top: 32%;
  width: 44px;
  height: 44px;
  border-radius: 999px 999px 999px 0;
  background: #ef4444;
  transform: rotate(-45deg);
  box-shadow: 180px 110px 0 #2563eb, -120px 140px 0 #16a34a;
}

.vb-gallery-twentynine-body {
  display: grid;
  gap: 8px;
  padding: 18px;
}

.vb-gallery-twentynine-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-twentynine-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 22px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-twentynine-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-twentynine-viewer[hidden] {
  display: none;
}

.vb-gallery-twentynine-viewer {
  position: fixed;
  inset: 0;
  z-index: 99999;
  display: grid;
  place-items: center;
  padding: 18px;
}

.vb-gallery-twentynine-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.66);
  backdrop-filter: blur(7px);
}

.vb-gallery-twentynine-modal {
  position: relative;
  z-index: 2;
  width: min(1060px, 100%);
  overflow: hidden;
  border-radius: 34px;
  background: #ffffff;
  box-shadow: 0 34px 120px rgba(15, 23, 42, 0.38);
}

.vb-gallery-twentynine-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.22);
  background: #ffffff;
}

.vb-gallery-twentynine-toolbar span {
  display: inline-flex;
  margin-bottom: 5px;
  padding: 5px 8px;
  border-radius: 999px;
  background: #eff6ff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 10px;
  font-weight: 950;
}

.vb-gallery-twentynine-toolbar strong {
  display: block;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 20px;
  line-height: 1.1;
  font-weight: 950;
}

.vb-gallery-twentynine-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
}

.vb-gallery-twentynine-tools button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  padding: 10px 13px;
  font-size: 12px;
  font-weight: 950;
  cursor: pointer;
}

.vb-gallery-twentynine-canvas {
  display: grid;
  place-items: center;
  height: min(66vh, 620px);
  overflow: hidden;
  background:
    linear-gradient(90deg, rgba(15, 23, 42, 0.06) 1px, transparent 1px),
    linear-gradient(0deg, rgba(15, 23, 42, 0.06) 1px, transparent 1px),
    #f8fafc;
  background-size: 30px 30px;
}

.vb-gallery-twentynine-hint {
  padding: 13px 16px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 850;
  text-align: center;
}

@media (max-width: 880px) {
  .vb-gallery-twentynine-head {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-twentynine-head strong {
    justify-content: center;
    width: 100%;
  }

  .vb-gallery-twentynine-grid {
    grid-template-columns: 1fr;
  }

  .vb-gallery-twentynine-toolbar {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-twentynine-tools {
    width: 100%;
    justify-content: flex-start;
  }
}

@media (max-width: 560px) {
  .vb-gallery-twentynine-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-twentynine-head h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-twentynine-modal {
    border-radius: 24px;
  }

  .vb-gallery-twentynine-large {
    width: 620px;
    height: 420px;
  }

  .vb-gallery-twentynine-tools button {
    flex: 1 1 auto;
  }
}

This zoom and pan image gallery is useful for product detail previews, artwork galleries, map interfaces, technical screenshots, architecture visuals, portfolio closeups, and any gallery where users need to inspect images more closely.

30. Image Gallery with Layout Density Controller

An image gallery with a layout density controller lets users decide how compact or spacious the gallery should feel. This is useful for media libraries, portfolio archives, product catalogs, inspiration boards, asset dashboards, and visual browsing pages where some users want large previews while others want more images visible at once.

This example uses JavaScript to control CSS custom properties. The density range input changes the gallery card size, spacing, preview height, and text visibility in real time. This is a different kind of JavaScript image gallery because the interaction controls the layout system itself instead of filtering or opening images.

JavaScript

(function () {
  const gallery = document.querySelector("[data-vb-gallery-thirty]");
  if (!gallery) return;

  const range = gallery.querySelector("[data-vb-gallery-thirty-range]");
  const label = gallery.querySelector("[data-vb-gallery-thirty-label]");

  const modes = {
    "1": {
      label: "Compact layout",
      min: "170px",
      gap: "10px",
      art: "150px",
      className: "is-compact"
    },
    "2": {
      label: "Balanced layout",
      min: "250px",
      gap: "16px",
      art: "230px",
      className: "is-balanced"
    },
    "3": {
      label: "Spacious layout",
      min: "330px",
      gap: "22px",
      art: "320px",
      className: "is-spacious"
    }
  };

  function applyDensity(value) {
    const mode = modes[value];

    gallery.style.setProperty("--vb-thirty-min", mode.min);
    gallery.style.setProperty("--vb-thirty-gap", mode.gap);
    gallery.style.setProperty("--vb-thirty-art", mode.art);

    gallery.classList.remove("is-compact", "is-balanced", "is-spacious");
    gallery.classList.add(mode.className);

    label.textContent = mode.label;
  }

  range.addEventListener("input", function () {
    applyDensity(range.value);
  });

  applyDensity(range.value);
})();

HTML

<div class="vb-gallery-thirty-demo">
  <div class="vb-gallery-thirty-shell" data-vb-gallery-thirty style="--vb-thirty-min: 250px; --vb-thirty-gap: 16px; --vb-thirty-art: 230px;">
    <div class="vb-gallery-thirty-head">
      <div>
        <span>Example 30</span>
        <h3>Image Gallery with Layout Density Controller</h3>
        <p>Use JavaScript to change gallery density, card size, spacing, and image height with CSS variables.</p>
      </div>

      <strong data-vb-gallery-thirty-label>Balanced layout</strong>
    </div>

    <div class="vb-gallery-thirty-control">
      <label for="vb-gallery-thirty-range">Gallery density</label>
      <input id="vb-gallery-thirty-range" type="range" min="1" max="3" value="2" data-vb-gallery-thirty-range>
      <div class="vb-gallery-thirty-scale">
        <span>Compact</span>
        <span>Balanced</span>
        <span>Spacious</span>
      </div>
    </div>

    <div class="vb-gallery-thirty-grid">
      <article class="vb-gallery-thirty-card">
        <div class="vb-gallery-thirty-art vb-gallery-thirty-art-orbit" aria-label="Orbit UI illustration" role="img"></div>
        <div class="vb-gallery-thirty-body">
          <span>Interface</span>
          <h4>Orbit UI</h4>
          <p>A CSS-only interface style image for adaptive gallery density.</p>
        </div>
      </article>

      <article class="vb-gallery-thirty-card">
        <div class="vb-gallery-thirty-art vb-gallery-thirty-art-studio" aria-label="Studio Object illustration" role="img"></div>
        <div class="vb-gallery-thirty-body">
          <span>Product</span>
          <h4>Studio Object</h4>
          <p>A product-style CSS illustration that changes size with the layout.</p>
        </div>
      </article>

      <article class="vb-gallery-thirty-card">
        <div class="vb-gallery-thirty-art vb-gallery-thirty-art-paper" aria-label="Paper Grid illustration" role="img"></div>
        <div class="vb-gallery-thirty-body">
          <span>Editorial</span>
          <h4>Paper Grid</h4>
          <p>An editorial CSS visual for responsive gallery card density.</p>
        </div>
      </article>

      <article class="vb-gallery-thirty-card">
        <div class="vb-gallery-thirty-art vb-gallery-thirty-art-wave" aria-label="Wave Scene illustration" role="img"></div>
        <div class="vb-gallery-thirty-body">
          <span>Abstract</span>
          <h4>Wave Scene</h4>
          <p>An abstract CSS image for testing compact and spacious views.</p>
        </div>
      </article>

      <article class="vb-gallery-thirty-card">
        <div class="vb-gallery-thirty-art vb-gallery-thirty-art-forest" aria-label="Forest Blocks illustration" role="img"></div>
        <div class="vb-gallery-thirty-body">
          <span>Nature</span>
          <h4>Forest Blocks</h4>
          <p>A CSS nature-inspired visual inside a density-aware gallery.</p>
        </div>
      </article>

      <article class="vb-gallery-thirty-card">
        <div class="vb-gallery-thirty-art vb-gallery-thirty-art-thermal" aria-label="Thermal Poster illustration" role="img"></div>
        <div class="vb-gallery-thirty-body">
          <span>Poster</span>
          <h4>Thermal Poster</h4>
          <p>A bold CSS poster card that adapts to the chosen layout density.</p>
        </div>
      </article>
    </div>
  </div>
</div>

CSS

.vb-gallery-thirty-demo,
.vb-gallery-thirty-demo * {
  box-sizing: border-box;
}

.vb-gallery-thirty-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 14% 14%, rgba(124, 58, 237, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.16), transparent 34%),
    linear-gradient(135deg, #f5f3ff 0%, #f0f9ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(124, 58, 237, 0.20);
  box-shadow: 0 28px 80px rgba(91, 33, 182, 0.10);
}

.vb-gallery-thirty-shell {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-gallery-thirty-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 22px;
}

.vb-gallery-thirty-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ede9fe;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-gallery-thirty-head h3 {
  max-width: 850px;
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-gallery-thirty-head p {
  max-width: 760px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-gallery-thirty-head strong {
  flex: 0 0 auto;
  display: inline-flex;
  padding: 11px 14px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-gallery-thirty-control {
  margin-bottom: 22px;
  padding: 18px;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.09);
}

.vb-gallery-thirty-control label {
  display: block;
  margin-bottom: 12px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 950;
}

.vb-gallery-thirty-control input {
  width: 100%;
  accent-color: #7c3aed;
}

.vb-gallery-thirty-scale {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
}

.vb-gallery-thirty-scale span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-gallery-thirty-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--vb-thirty-min), 1fr));
  gap: var(--vb-thirty-gap);
  transition: gap 0.22s ease;
}

.vb-gallery-thirty-card {
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.10);
  transition: transform 0.22s ease, border-radius 0.22s ease;
}

.vb-gallery-thirty-card:hover {
  transform: translateY(-5px);
}

.vb-gallery-thirty-art {
  position: relative;
  height: var(--vb-thirty-art);
  overflow: hidden;
  background: #111827;
  transition: height 0.22s ease;
}

.vb-gallery-thirty-art::before,
.vb-gallery-thirty-art::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.vb-gallery-thirty-art-orbit {
  background:
    radial-gradient(circle at 50% 50%, rgba(255,255,255,0.90) 0 4%, transparent 5%),
    radial-gradient(circle at 50% 50%, transparent 0 24%, rgba(125,211,252,0.42) 25% 26%, transparent 27%),
    radial-gradient(circle at 50% 50%, transparent 0 38%, rgba(196,181,253,0.42) 39% 40%, transparent 41%),
    linear-gradient(135deg, #020617, #312e81 55%, #0f172a);
}

.vb-gallery-thirty-art-orbit::before {
  left: 22%;
  top: 22%;
  width: 54px;
  height: 54px;
  border-radius: 999px;
  background: #38bdf8;
  box-shadow: 240px 120px 0 #c4b5fd, 130px 20px 0 #ffffff;
}

.vb-gallery-thirty-art-orbit::after {
  left: 50%;
  top: 50%;
  width: 170px;
  height: 170px;
  border-radius: 999px;
  border: 2px dashed rgba(255,255,255,0.32);
  transform: translate(-50%, -50%) rotate(-12deg);
}

.vb-gallery-thirty-art-studio {
  background: radial-gradient(circle at 50% 42%, #ffffff 0%, #e0e7ff 34%, #312e81 100%);
}

.vb-gallery-thirty-art-studio::before {
  left: 50%;
  top: 50%;
  width: 150px;
  height: 150px;
  border-radius: 34px;
  background: linear-gradient(135deg, #ffffff, #c7d2fe);
  box-shadow: 0 30px 80px rgba(49,46,129,0.34);
  transform: translate(-50%, -50%) rotate(18deg);
}

.vb-gallery-thirty-art-studio::after {
  left: 50%;
  top: 50%;
  width: 78px;
  height: 78px;
  border-radius: 24px;
  border: 5px solid rgba(79,70,229,0.50);
  transform: translate(-50%, -50%) rotate(18deg);
}

.vb-gallery-thirty-art-paper {
  background:
    linear-gradient(90deg, rgba(15,23,42,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(15,23,42,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #f8fafc, #e2e8f0);
  background-size: 26px 26px;
}

.vb-gallery-thirty-art-paper::before {
  left: 16%;
  top: 18%;
  width: 68%;
  height: 62%;
  border-radius: 24px;
  background:
    linear-gradient(90deg, #0f172a 0 18%, transparent 18%),
    linear-gradient(#64748b 0 0) 32% 28% / 48% 5px no-repeat,
    linear-gradient(#94a3b8 0 0) 32% 44% / 42% 5px no-repeat,
    linear-gradient(#cbd5e1 0 0) 32% 60% / 52% 5px no-repeat,
    #ffffff;
  box-shadow: 0 24px 70px rgba(15,23,42,0.16);
}

.vb-gallery-thirty-art-paper::after {
  right: 18%;
  bottom: 18%;
  width: 72px;
  height: 72px;
  border-radius: 18px;
  background: #38bdf8;
  transform: rotate(14deg);
}

.vb-gallery-thirty-art-wave {
  background: linear-gradient(135deg, #0f172a, #0369a1 52%, #22d3ee);
}

.vb-gallery-thirty-art-wave::before {
  left: -10%;
  right: -10%;
  bottom: 12%;
  height: 46%;
  border-radius: 50% 50% 0 0;
  background:
    radial-gradient(circle at 20% 30%, rgba(255,255,255,0.56), transparent 12%),
    radial-gradient(circle at 56% 34%, rgba(255,255,255,0.38), transparent 14%),
    linear-gradient(135deg, rgba(255,255,255,0.28), rgba(255,255,255,0.06));
}

.vb-gallery-thirty-art-wave::after {
  left: 16%;
  top: 18%;
  width: 92px;
  height: 92px;
  border-radius: 999px;
  background: rgba(255,255,255,0.34);
  filter: blur(4px);
}

.vb-gallery-thirty-art-forest {
  background: linear-gradient(180deg, #dcfce7 0%, #22c55e 54%, #052e16 100%);
}

.vb-gallery-thirty-art-forest::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 72%;
  background:
    conic-gradient(from 45deg at 18% 100%, transparent 0 25%, #052e16 0 50%, transparent 0),
    conic-gradient(from 45deg at 50% 100%, transparent 0 25%, #166534 0 50%, transparent 0),
    conic-gradient(from 45deg at 82% 100%, transparent 0 25%, #052e16 0 50%, transparent 0);
}

.vb-gallery-thirty-art-forest::after {
  right: 16%;
  top: 16%;
  width: 80px;
  height: 80px;
  border-radius: 999px;
  background: rgba(240,253,244,0.82);
  filter: blur(4px);
}

.vb-gallery-thirty-art-thermal {
  background:
    radial-gradient(circle at 30% 30%, #fef3c7 0 10%, transparent 11%),
    radial-gradient(circle at 70% 70%, #fb7185 0 16%, transparent 17%),
    linear-gradient(135deg, #7f1d1d, #e11d48 52%, #f97316);
}

.vb-gallery-thirty-art-thermal::before {
  left: 50%;
  top: 50%;
  width: 170px;
  height: 170px;
  border-radius: 42% 58% 62% 38%;
  background: rgba(255,255,255,0.22);
  transform: translate(-50%, -50%) rotate(22deg);
  backdrop-filter: blur(5px);
}

.vb-gallery-thirty-art-thermal::after {
  left: 22%;
  bottom: 18%;
  width: 58%;
  height: 9px;
  border-radius: 999px;
  background: rgba(255,255,255,0.48);
  box-shadow: 0 24px 0 rgba(255,255,255,0.30), 0 48px 0 rgba(255,255,255,0.18);
}

.vb-gallery-thirty-body {
  display: grid;
  gap: 8px;
  padding: clamp(14px, 2vw, 18px);
}

.vb-gallery-thirty-body span {
  display: inline-flex;
  width: fit-content;
  padding: 6px 9px;
  border-radius: 999px;
  background: #ede9fe;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-gallery-thirty-body h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 21px !important;
  line-height: 1.14 !important;
  font-weight: 950 !important;
  letter-spacing: -0.045em;
}

.vb-gallery-thirty-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.58;
  font-weight: 650;
}

.vb-gallery-thirty-shell.is-compact .vb-gallery-thirty-body p {
  display: none;
}

.vb-gallery-thirty-shell.is-compact .vb-gallery-thirty-card {
  border-radius: 22px;
}

.vb-gallery-thirty-shell.is-spacious .vb-gallery-thirty-card {
  border-radius: 34px;
}

@media (max-width: 820px) {
  .vb-gallery-thirty-head {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-gallery-thirty-head strong {
    justify-content: center;
    width: 100%;
  }
}

@media (max-width: 560px) {
  .vb-gallery-thirty-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-gallery-thirty-head h3 {
    font-size: 36px !important;
    letter-spacing: -0.055em;
  }

  .vb-gallery-thirty-scale span {
    font-size: 11px;
  }
}

This layout density image gallery is useful for media libraries, design inspiration boards, product catalogs, portfolio archives, asset dashboards, and responsive gallery interfaces where users should control how compact or spacious the gallery feels.

JavaScript image galleries work best when the visual design, data structure, interaction logic, accessibility, and performance choices are planned together. A gallery should not be only a decorative grid. It should help users browse images, understand categories, open previews, search content, move between items, and use the gallery comfortably on desktop and mobile screens.

The most important best practice is to decide what the gallery should do before writing the code. A portfolio gallery may need filters and case study labels. A product image gallery may need thumbnails, zoom, variant previews, and a clear active image state. A media library may need search, tags, sorting, selection, and pagination. A photography gallery may need a lightbox, keyboard navigation, focus handling, and fullscreen viewing.

For WordPress and Gutenberg posts, it is also important to keep class names unique. If several examples use the same class names or generic IDs, one script can break another example. In this guide, every JavaScript image gallery example uses its own wrapper and scoped selectors so the examples can work together inside one long article.

A responsive image gallery should feel natural on every screen size. On desktop, users may want multi-column layouts, large previews, filters, side panels, and hover effects. On mobile, the same gallery needs larger tap targets, simpler controls, stacked cards, scrollable filters, and image previews that do not overflow the viewport.

The safest approach is to design the gallery layout with flexible CSS grid rules, fluid image heights, and clear mobile breakpoints. Avoid fixed-width gallery cards unless they are inside a scrollable container. Keep lightbox controls reachable on mobile, and make sure close buttons, next buttons, search fields, filters, and thumbnails stay easy to tap.

Common JavaScript image gallery mistakes usually happen when the gallery is designed only for the first visual preview and not for real content, mobile devices, accessibility, or long-term maintenance. A gallery may look good with three demo images but break when the website owner adds 40 images, long titles, multiple categories, or larger files.

The best way to avoid gallery problems is to think about the full user flow. What happens when no filter matches? What happens when an image fails to load? Can the user close the modal with Escape? Does the gallery work with keyboard controls? Does the layout break on phones? Does the script still work when two galleries exist on the same page?

Here are common questions about JavaScript image galleries for portfolio websites, ecommerce product pages, photography galleries, real estate listings, media libraries, agency case studies, responsive landing pages, and WordPress posts.

JavaScript image galleries are useful for websites where visual content is important. They can improve portfolio pages, photography galleries, ecommerce product pages, real estate listings, travel blogs, agency case studies, media libraries, dashboards, image upload interfaces, landing pages, and product showcase sections.

The best image gallery is not only a collection of pictures. It has a clear browsing structure, useful JavaScript behavior, responsive layout, accessible controls, optimized loading, strong visual hierarchy, and a simple way for users to explore images without confusion.

You can use the examples in this guide as starting points for real website projects. Replace the CSS-only demo visuals with real images, connect the JavaScript data arrays to your own content, adjust the filters and tags, customize the design, and keep the gallery logic focused on what users actually need to do.

Continue building better website sections and interactive UI components with these related JavaScript and CSS guides. These posts work well together when building full landing pages, product pages, portfolios, dashboards, navigation systems, filters, forms, and responsive website layouts.