Skip to content

Components / Sections

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.

Kind
Bedrooms

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

tone
density
columns

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

Baseline · newly available

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:

terminal
npx shadcn@latest add @moonarc-pro/listing-grid
src/pages/index.astro
---
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

PropTypeDefaultDescription
listings{ href; title; address?; price; kind; beds; baths?; area?; badge?; art; transition? }[]noneThe 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 }[] }noneThe kind filter. Unset, none.
beds{ legend; any; options: number[] }noneThe bedrooms filter: the least numbers offered. Unset, none.
words{ count?; one?; none?; bed?; beds?; bath?; baths? }EnglishThe count ({n} is the number), its singular, the note when nothing matches and the units.
columns2 | 3 | 43Cards a row on a wide screen.
headlinestringnoneThe heading. Slot "headline" wins.
eyebrowstringnoneA short line above the heading.
copystringnoneA line under the heading.
level1 | 2 | 32Heading 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

Replaces: Listing grids filtered with a client-side script · IDX listing widgets for simple sites. See the migration table.

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.

newly · Chrome 137 · Firefox 144 · Safari 18.4click

Home Art

Backgrounds

A 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.

every browserpointer

The Boathouse

Southbank, on the river path

$1,150,000 Guide price (a sample)

Bedrooms
4
Bathrooms
3
Floor area
204 m²
Built
2019

Book a viewing

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.

newly · Chrome 105 · Firefox 121 · Safari 15.4clickpointer

The 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.

every browserload

Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown