Muuri Filterable Draggable Grid — Free JS Snippet

Muuri Filterable, Sortable and Draggable Grid · Layouts · Plain HTML, CSS & JS · Live preview

CategoryLayouts

What's included

Features

Filtering, sorting, shuffling and drag-to-reorder in one layout engine
Masonry-style fillGaps packing for cards of different heights
Single filter predicate combining category chips and search
Sort and shuffle operate on the same list the user can drag
Tuned drag sorting heuristics and a soft release animation
Animated show, hide and layout transitions
Live shown-count from the filter event
Layout refreshed on window resize

About this UI Snippet

Muuri Filterable, Sortable and Draggable Grid — HTML, CSS & JavaScript

Screenshot of the Muuri Filterable, Sortable and Draggable Grid snippet rendered live

Most grid libraries do one of three things. Isotope filters and sorts a masonry layout. Sortable and Dragula let you drag items. Masonry packs items into tight columns. Muuri is unusual in doing all of them at once, in the same layout engine: a grid whose items can be filtered by category, ordered by any function, rearranged by hand and packed like masonry, with every change animated. That combination is what makes it the natural choice for portfolio pages, project boards and dashboards, where users both search and curate.

The core setup is a container and a set of items. new Muuri(container, options) positions each element absolutely, calculates a packed layout and animates items into place; the items themselves must have their own wrapper with the positioning and padding, while the inner card carries the visual styling, which is why the CSS separates .mu-item from .mu-card. layout: { fillGaps: true } turns on the masonry behaviour: when a tall card leaves a hole beside it, a later, shorter card is slotted into the gap instead of being pushed to the next row. Card heights are set individually here, which is what makes the effect visible.

Filtering is a predicate. muuri.filter(function (item) { ... }) receives every item and returns whether it should be shown; the snippet's predicate combines the selected category chip with the search text, so both controls work together instead of one clearing the other. Hidden items animate away and the rest close ranks. The filter event reports the shown items, which drives the count line. Sorting is similarly a comparator: muuri.sort(fn) reorders the underlying item list, and the Shuffle button uses a random comparator. Because sort and drag act on the same list, you can sort A–Z, then drag one card to override its position, and the layout stays consistent — there is no separate "manual order" to reconcile.

Dragging is enabled with dragEnabled: true, and the dragSortHeuristics options tune how quickly items swap as you drag: sortInterval throttles the calculation and minDragDistance avoids accidental drags. dragRelease gives the dropped card a soft settle animation. Because layout is computed from item sizes, the snippet calls refreshItems().layout() when the window resizes, since Muuri has no way to know the percentage widths changed. The web-animations polyfill that older Muuri versions needed is not required in current browsers.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant like Claude to persist the manual order, add multi-select with bulk actions, or add drag between two grids.

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 filterable, sortable, draggable masonry grid with Muuri 0.9 loaded from a CDN.

Requirements:
- Create new Muuri(grid, { items, layoutDuration, layout: { fillGaps: true }, dragEnabled: true, dragSortHeuristics, dragRelease }) with twelve project cards of different heights, each inside a positioned wrapper.
- Add category chips (with aria-pressed) and a search box; both feed a single muuri.filter predicate, and a filter event handler shows how many cards are visible.
- Add Sort A-Z and Shuffle buttons using muuri.sort with a comparator.
- Call muuri.refreshItems().layout() on window resize.

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.

Source Code

Requires
<div class="mu-app">
  <div class="mu-bar">
    <input id="muSearch" type="search" placeholder="Search projects..." aria-label="Search projects">
    <div class="mu-chips" id="muChips" role="group" aria-label="Filter by category"></div>
    <div class="mu-tools">
      <button type="button" id="muSort">Sort A&ndash;Z</button>
      <button type="button" id="muShuffle">Shuffle</button>
    </div>
  </div>
  <div class="mu-count" id="muCount" aria-live="polite"></div>
  <div class="mu-grid" id="muGrid"></div>
</div>

Step by step

How to Use

  1. 1
    Filter by categoryClick Design, Engineering, Marketing or Research. Other cards animate away and the rest close ranks.
  2. 2
    SearchType in the search box. It combines with the selected category chip.
  3. 3
    Drag a cardDrag any card to a new place. Neighbours slide aside as you pass over them.
  4. 4
    Sort or shufflePress Sort A–Z or Shuffle to reorder all visible cards, then drag one to override.
  5. 5
    Notice the packingCards of different heights pack tightly, with short ones filling gaps beside tall ones.

Real-world uses

Common Use Cases

Portfolios and project galleries
Let visitors filter work and curate what they see. For a card-focused layout with a sliding row see the Swiper multi-row grid.
Personal boards and bookmark walls
Search and rearrange saved items freely.
SHOP
Wishlists and comparison walls
Group and reorder products the user is considering.
Learning combined layout features
A rare example where filter, sort and drag share one source of truth.

Got questions?

Frequently Asked Questions

Muuri combines filtering, sorting, masonry packing and drag-and-drop in one engine, so all of them work on the same layout.

Call muuri.filter with a predicate function, or a selector string, that returns true for items to show.

Write one predicate that checks both conditions, and call it whenever either control changes.

Muuri positions the outer item absolutely, so padding and visuals go on an inner element to avoid layout glitches.

Set layout: { fillGaps: true } so smaller items fill gaps left by taller ones.

Call muuri.refreshItems().layout() when the container or item sizes change, as Muuri does not detect this automatically.

Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from Muuri, so in a framework project install it with npm install muuri instead of the CDN tag, create it in useEffect / onMounted / ngAfterViewInit and re-run layout() after the items change, and release it with destroy() when the component unmounts.