Filter buttons over a grid of cards: the cards that stay slide to their new places, the ones that go fade out, the ones that return fade in.
Listing Grid
Homes as cards (the building drawn by HomeArt, the price, the address, the beds, the baths and the area) with filters that work with no script: a group of radios for the kind of home and one for the least bedrooms, and :has() rules written for the list at build hide every card that does not match. The count over the cards and the note when nothing matches are worked out at build for every pair of choices, so they are words in the page, read out as they change. A card can carry a view-transition-name so it grows into its own page. Zero JavaScript.
Live demo
Homes for sale
Pick a kind and the least bedrooms: the cards filter with no script. Sample homes and prices.
6 homes6 homes5 homes3 homes2 homes3 homes3 homes3 homes3 homes2 homes3 homes3 homes2 homes0 homes0 homes
No home matches. Try fewer bedrooms or another kind.No home matches. Try fewer bedrooms or another kind.
Live, from the library itself, at full size: scroll, hover, navigate away and back. The demo is never gated.
Variants · each row writes the attribute the prop writes; the section's own CSS answers
Measured
JavaScript of its own
0 B
Uses Reveal and the shared runtime (1.9 kB raw, once per site). With those included: 3.0 kB raw.
CSS 16.1 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.
Browser support
Chrome 105 · Firefox 121 · Safari 15.4; needs has. Elsewhere: without :has() every card shows and the filters do nothing.
Install Pro
The demo above is never gated; the source is. With a key in components.json (see account), the registry serves it like any other item:
npx shadcn@latest add @moonarc-pro/listing-grid---
import ListingGrid from '@/components/moonarc/pro/ListingGrid.astro';
---
<ListingGrid
headline="Homes for sale"
kinds={{ legend: 'Kind', all: 'All', options: [{ value: 'house', label: 'Houses' }, { value: 'apartment', label: 'Flats' }] }}
beds={{ legend: 'Bedrooms', any: 'Any', options: [1, 2, 3, 4] }}
listings={[
{ href: '/homes/12-linden-row/', title: '12 Linden Row', address: 'Linden Hill', price: '$845,000', kind: 'house', beds: 4, baths: 2, area: '142 m²', art: { kind: 'house', seed: '12 Linden Row', block: 2 } },
{ href: '/homes/4-quay-street/', title: '4 Quay Street', address: 'Old Harbour', price: '$520,000', kind: 'apartment', beds: 2, baths: 1, area: '74 m²', badge: 'New', art: { kind: 'apartment', seed: '4 Quay Street', block: 3 } },
]}
/>Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: home-art, reveal.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
listings | { href; title; address?; price; kind; beds; baths?; area?; badge?; art; transition? }[] | none | The homes. kind is the value the kind filter matches; price and area are written as given; art is HomeArt's kind, seed, time and block. |
kinds | { legend; all; options: { value; label }[] } | none | The kind filter. Unset, none. |
beds | { legend; any; options: number[] } | none | The bedrooms filter: the least numbers offered. Unset, none. |
words | { count?; one?; none?; bed?; beds?; bath?; baths? } | English | The count ({n} is the number), its singular, the note when nothing matches and the units. |
columns | 2 | 3 | 4 | 3 | Cards a row on a wide screen. |
headline | string | none | The heading. Slot "headline" wins. |
eyebrow | string | none | A short line above the heading. |
copy | string | none | A line under the heading. |
level | 1 | 2 | 3 | 2 | Heading level of the heading; each card's title is one below. 1 under a page's own heading with no heading of the grid's own: the titles are h2. |
tone | 'surface' | 'muted' | 'ink' | 'brand' | unset (paints nothing) | The section's ground. |
density | 'compact' | 'cozy' | 'roomy' | 'cozy' | Block padding ×0.8 / ×1 / ×1.25 through --ma-density. |
Theming and overrides
The section reads the theme contract (shadcn's variable names with the base tokens as fallback), so a pasted theme or one --ma-brand recolours it. Its class names are the override surface and stay stable; a named slot replaces the matching prop with your own markup.
Classes
.ma-lgrid.ma-lgrid__address.ma-lgrid__art.ma-lgrid__badge.ma-lgrid__body.ma-lgrid__card.ma-lgrid__cards.ma-lgrid__chip.ma-lgrid__copy.ma-lgrid__count.ma-lgrid__eyebrow.ma-lgrid__facts.ma-lgrid__filters.ma-lgrid__group.ma-lgrid__head.ma-lgrid__headline.ma-lgrid__inner.ma-lgrid__legend.ma-lgrid__link.ma-lgrid__none.ma-lgrid__price.ma-lgrid__radio.ma-lgrid__reveal.ma-lgrid__title
Slots
slot="headline"
Reduced motion
The cards arrive at once; a filter hides and shows them with no movement.
With ClientRouter
CSS and Reveal; a filter picked is the page's own state and resets with the page.
Why it is built this way
- The filters are radios in fieldsets, so the keyboard moves along them and a screen reader hears each group's legend. The rules that hide a card are written for this list at build (a card carries data-kind and the thresholds it is under in data-below), printed once beside it.
- The count over the cards is one sentence for each pair of choices, written at build, the matching one shown in a polite live region: it is a word in the page, where a CSS counter would be a picture of a number.
- The note when nothing matches is written only for the pairs that match nothing, so it never shows over a card.
Replaces: Listing grids filtered with a client-side script · IDX listing widgets for simple sites. See the migration table.
Related
Home Art
BackgroundsA building drawn flat, for a listing that has no photograph yet: a house, a terrace, an apartment block, a villa, a cabin, a tower, an office or a shop, in the colour of the block or tone it sits on, with a shade, a light and the windows mixed from it.
The Boathouse
Southbank, on the river path
$1,150,000 Guide price (a sample)
- Bedrooms
- 4
- Bathrooms
- 3
- Floor area
- 204 m²
- Built
- 2019
The top of a home's own page: its pictures on one side, each a building drawn by HomeArt under a Lens that magnifies it at the pointer, with a row of thumbnails that picks one (radios and :has(), so the arrow keys move along them); on the other side the price, the title, the address, the facts and two actions.
Listing Hero
ProThe first screen of an estate agency: the headline, a line, two actions and a few figures on one side, and on the other the homes on the market now, one large and two small, each its building drawn by HomeArt with the price and the street on a card over it, every one a link to its page.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown