Examples
Semantic List Grid
Render masonry as semantic list or grid markup.
Masonry layouts can still be semantic. Use as, itemAs, role, and
aria-label to match the surrounding page. This example uses
MasonryBalanced so the li elements stay direct children of the ul.
Example
import { clsx } from 'clsx';
import { MasonryBalanced } from 'masonix';
type Product = {
id: string;
name: string;
category: string;
price: string;
rating: string;
badge: string;
gradient: string;
height: number;
};
function ProductCard({ product }: { product: Product }) {
return (
<article
className={clsx(
'overflow-hidden',
'rounded-xl border',
'border-zinc-200 bg-white text-zinc-950 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50',
)}
>
<div
className="relative"
style={{ height: product.height, background: product.gradient }}
>
<span
className={clsx(
'absolute left-3 top-3',
'px-2 py-1',
'rounded-full',
'text-xs font-medium',
'bg-white/85 text-zinc-900',
)}
>
{product.badge}
</span>
</div>
<div className="p-4">
<p className="text-xs text-zinc-500 dark:text-zinc-400">
{product.category}
</p>
<h3 className="mt-1 text-sm font-semibold">{product.name}</h3>
<p className="mt-3 text-xs text-zinc-500 dark:text-zinc-400">
Rating {product.rating} / 5.0
</p>
</div>
</article>
);
}
export function FeaturedProducts({ products }: { products: Product[] }) {
return (
<MasonryBalanced
as="ul"
itemAs="li"
role="list"
aria-label="Featured products"
className="m-0 list-none p-0"
itemClassName="m-0 list-none p-0"
items={products.slice(0, 4)}
columnWidth={210}
maxColumns={2}
gap={16}
estimatedItemHeight={360}
itemKey={(product) => product.id}
render={({ data }) => <ProductCard product={data} />}
/>
);
}When to use
Use this when the masonry content is a real list of products, articles, resources, or search results.
Notes
role="none"suppresses Masonix roles when outer markup already has meaning.- Preserve meaningful reading order in the
itemsarray. - Prefer
MasonryBalancedorMasonryVirtualfor strictul/limarkup. - Use
aria-labelwhen the list needs a name.