Filter Tabs Gallery — Free HTML CSS JS Filterable Image Grid Snippet

Filter Tabs Gallery · Navigation · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Category filtering driven entirely by data-category / data-filter attributes
Fade-and-scale exit transition instead of an abrupt display:none jump
Single-pass filterGallery() function scales to large grids without slowdown
Responsive grid — 4 columns on desktop, 2 on mobile via a single media query
Active tab styled with a solid pill background for clear visual state
Hidden items collapse their box so the grid reflows without gaps
Add unlimited categories with zero JavaScript changes
No external image or icon library required — gradient placeholders included
Fully keyboard-clickable buttons with role="tablist" for screen readers

About this UI Snippet

Filter Tabs Gallery — HTML, CSS & JavaScript Filterable Grid

Screenshot of the Filter Tabs Gallery snippet rendered live

Portfolio sites, stock photo pages, and product catalogs all need a way to narrow a large grid down to a relevant subset without a page reload. This snippet pairs a row of pill-shaped filter tabs with a responsive image grid, where clicking a tab instantly shows only the matching items with a soft fade-and-scale transition.

The gallery uses a single source of truth: every .gallery-item carries a data-category attribute (nature, city, or people). The filter tabs carry a matching data-filter attribute. No duplicate lists or hidden state to keep in sync — the DOM attribute is the state.

How the filtering logic works

Clicking a tab calls filterGallery(btn), which reads btn.dataset.filter. It then loops over every .gallery-item and checks whether filter === 'all' or the item's dataset.category matches. Items that don't match get the .hidden class toggled on; items that do match get it removed. This is a single pass over the grid, so it scales fine to a few hundred items without any noticeable delay.

How the fade-and-scale transition works

Rather than just toggling display: none, hidden items transition their opacity to 0 and transform: scale(0.85) down slightly, using a 0.25s CSS transition. Once faded out, the .hidden class also collapses the item's box (width/height: 0, position: absolute, overflow: hidden) so the remaining visible items reflow into a clean grid instead of leaving gaps. Because the collapse properties and the opacity/transform properties are on the same class, the fade plays first visually before the layout collapses — the effect reads as a gentle "items melt away" rather than an abrupt jump.

Extending the categories

Add a new tab button with a new data-filter value, then tag any gallery items with the matching data-category. No JavaScript changes are required — the loop works for any number of categories.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet into an AI coding assistant and ask it to walk through why the .hidden class combines opacity/transform transitions with box-collapsing properties in the same class — that combination is what makes the exit feel like a smooth fade instead of a layout jump. You can also ask it to convert the single-select filter into a multi-select one using a Set of active categories, or to add a "count" badge on each tab showing how many items match. It's a good exercise in seeing how far you can push a pattern using only data attributes as state before you actually need a JS framework.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "filter tabs gallery" in plain HTML, CSS, and vanilla JavaScript.

Requirements:
- A row of pill-shaped filter buttons (All, plus at least three specific categories), each carrying a data-filter attribute, with exactly one marked active at a time.
- A responsive image grid below the tabs where every grid item carries a data-category attribute matching one of the filter values.
- A single filterGallery(btn) function that reads the clicked button's data-filter, updates the active class on the tabs, and loops once over all grid items toggling a .hidden class based on whether data-category matches the filter (or the filter is "all").
- The .hidden state must combine a CSS opacity/transform transition (for a fade-and-scale effect) with box-collapsing (zero width/height, absolute positioning) so that hidden items don't leave gaps and visible items reflow into a clean grid.
- The grid must be responsive (at least 4 columns on desktop collapsing to 2 on narrow viewports) using a single media query.
- No external libraries, no build step, and the pattern must support adding new categories purely by adding new markup — no JavaScript changes.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Load the snippetClick "Filter Tabs Gallery" in the sidebar Library tab to load the HTML, CSS, and JS panels.
  2. 2
    Click through the filtersTry All, Nature, City, and People in the preview to see the fade-and-scale filtering in action.
  3. 3
    Swap in your own imagesReplace the gradient .thumb divs with <img> tags, or set background-image on each .thumb in the CSS panel.
  4. 4
    Add more categoriesAdd a new filter tab with a data-filter value and tag matching gallery items with the same data-category. No JS changes needed.
  5. 5
    Adjust the transitionTune the 0.25s duration or the scale(0.85) value in the CSS panel to make the filter feel snappier or slower.
  6. 6
    Export and saveUse the export buttons for HTML, JSX, or Tailwind, or click "Save as" to store your version in IndexedDB.

Real-world uses

Common Use Cases

GALLERY
Portfolio and photography sites
Let visitors filter a body of work by category without leaving the page or triggering a reload.
SHOP
Product or catalog grids
Swap category names for product types (Shirts / Shoes / Accessories) and reuse the same filtering logic.
Learn attribute-driven UI state
Study how data-* attributes act as the single source of truth instead of maintaining a separate JS array of items.
Prototype content-heavy pages
Quickly mock up a filterable media library or asset browser before wiring it to a real backend.
Blog or article tag filters
Adapt the same pattern to filter blog post cards by tag instead of image category.
Multi-select filtering
Use this single-select version as a base, then extend filterGallery to support multiple active tags with a Set instead of a single string.

Got questions?

Frequently Asked Questions

Every gallery item has a data-category attribute. When a tab is clicked, filterGallery reads the tab's data-filter value and compares it against each item's data-category, toggling a .hidden class on non-matches.

Keeping the category directly on the DOM element means there is only one place to update when content changes — no risk of a JS array getting out of sync with the actual markup.

The base version supports one active filter at a time. To support multi-select, track selected filters in a Set, toggle tabs individually instead of clearing all, and check item.dataset.category against the Set with .has().

Add a button with data-filter="animals" to the .filter-tabs row, and add data-category="animals" to any gallery-item you want included. No JavaScript edits are required.

The .hidden class both fades the opacity/scale and collapses the box dimensions. This lets the remaining visible items reflow cleanly into the grid instead of leaving empty gaps where filtered-out items used to be.

Yes. Replace the .thumb divs with <img src="..." class="thumb"> tags, or keep the divs and set background-image and background-size: cover in CSS.

Yes, the filtering loop is O(n) over the visible items and runs in under a millisecond for typical gallery sizes (dozens to a few hundred items).