Catalog layout with category, price, and color filters beside a sortable product grid.
42 results in Courses and Templates
Course · 42 lessons
Asset pack · 60 presets
Template · instant access
Ebook · 180 pages
Replay · 3 hours
Audio · 120 loops
Install with the CLI, or add the packages and paste the source below.
Add Product Grid to your app with its source-owned dependencies.
npx cronus-ui add product-grid
Install the Cronus UI packages, then paste the block below.
Copy the source for With filters. Every class is a semantic token, so it re-themes with your app.
import {Badge,Button,Card,CardFooter,CardHeader,CardTitle,Checkbox,cn,Label,Select,SelectContent,SelectItem,SelectTrigger,SelectValue,Separator,Slider,} from "@cronus-ui/ui";import { PRODUCTS } from "../lib/demo-store.js";import { ArrowUpRight, SlidersHorizontal } from "lucide-react";interface Product {id: string;title: string;kind: string;price: string;gradient: string;initials: string;badge?: string;}// The storefront grid shows the digital creator line from the shared demo-store// dataset (everything that is not the physical Audio/Accessories catalog), so the// same products stay in sync across the grid, cart, and order surfaces. Prices are// whole dollars here (e.g. `$129`), derived from the canonical `priceValue`.const products: Product[] = PRODUCTS.filter((p) => p.category !== "Audio" && p.category !== "Accessories",).map((p) => ({id: p.id,title: p.name,kind: p.kind,price: `$${p.priceValue}`,gradient: p.gradient,initials: p.initials,badge: p.badge,}));interface CatalogCategory {id: string;label: string;count: number;checked: boolean;}const catalogCategories: CatalogCategory[] = [{ id: "filter-courses", label: "Courses", count: 24, checked: true },{ id: "filter-templates", label: "Templates", count: 18, checked: true },{ id: "filter-ebooks", label: "Ebooks", count: 12, checked: false },{ id: "filter-audio", label: "Audio", count: 9, checked: false },{ id: "filter-workshops", label: "Workshops", count: 6, checked: false },];interface CatalogSwatch {id: string;label: string;dotClass: string;selected: boolean;}const catalogSwatches: CatalogSwatch[] = [{ id: "violet", label: "Violet", dotClass: "bg-primary", selected: true },{ id: "sky", label: "Sky", dotClass: "bg-info", selected: false },{ id: "emerald", label: "Emerald", dotClass: "bg-success", selected: false },{ id: "amber", label: "Amber", dotClass: "bg-warning", selected: false },{ id: "rose", label: "Rose", dotClass: "bg-error", selected: false },];export function ProductGridWithFiltersBlock() {return (<section aria-label="Product catalog" className="grid w-full gap-6 lg:grid-cols-[240px_1fr]">{/* Filter sidebar */}<fieldsetaria-label="Product filters"className="m-0 flex h-fit min-w-0 flex-col gap-5 rounded-2xl border border-border bg-surface-raised p-5"><div className="flex items-center justify-between"><span className="flex items-center gap-2 text-sm font-semibold text-fg"><SlidersHorizontal className="size-4 text-fg-tertiary" aria-hidden="true" />Filters</span><buttontype="button"className="text-xs font-medium text-primary-strong underline-offset-4 hover:underline">Clear all</button></div><div className="flex flex-col gap-3"><span className="text-xs font-medium uppercase tracking-wide text-fg-tertiary">Category</span>{catalogCategories.map((category) => (<Labelkey={category.id}htmlFor={category.id}className="flex items-center gap-2 font-normal text-fg-secondary"><Checkbox id={category.id} defaultChecked={category.checked} /><span className="flex-1">{category.label}</span><span className="text-xs text-fg-tertiary">{category.count}</span></Label>))}</div><Separator /><div className="flex flex-col gap-3"><div className="flex items-center justify-between"><span className="text-xs font-medium uppercase tracking-wide text-fg-tertiary">Price</span><span className="text-xs text-fg-secondary">$19 – $129</span></div><Slider defaultValue={[19, 129]} min={0} max={200} step={1} aria-label="Price range" /></div><Separator /><div className="flex flex-col gap-3"><span className="text-xs font-medium uppercase tracking-wide text-fg-tertiary">Color</span><div className="flex items-center gap-2">{catalogSwatches.map((swatch) => (<buttonkey={swatch.id}type="button"aria-pressed={swatch.selected}className={cn("size-6 rounded-full",swatch.dotClass,swatch.selected &&"ring-2 ring-border-strong ring-offset-2 ring-offset-surface-base",)}><span className="sr-only">Filter by color: {swatch.label}</span></button>))}</div></div></fieldset>{/* Results */}<div className="flex flex-col gap-4"><header className="flex flex-wrap items-center justify-between gap-3"><div className="flex flex-col gap-1"><h2 className="font-display text-2xl font-semibold text-fg">All products</h2><p className="text-sm text-fg-secondary">42 results in Courses and Templates</p></div><Select defaultValue="featured"><SelectTrigger className="w-44" aria-label="Sort products"><SelectValue /></SelectTrigger><SelectContent><SelectItem value="featured">Featured</SelectItem><SelectItem value="newest">Newest</SelectItem><SelectItem value="price-asc">Price: Low to high</SelectItem><SelectItem value="price-desc">Price: High to low</SelectItem></SelectContent></Select></header><div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">{products.map((product) => (<Card key={product.id} className="gap-0 overflow-hidden pt-0 shadow-sm"><divclassName={cn("relative flex aspect-[16/10] items-center justify-center bg-gradient-to-br",product.gradient,)}><span className="font-display text-3xl font-semibold text-fg/70">{product.initials}</span>{product.badge ? (<div className="absolute start-3 top-3"><Badge variant="primary">{product.badge}</Badge></div>) : null}</div><CardHeader className="pt-4"><CardTitle className="font-display text-base">{product.title}</CardTitle><p className="col-span-full text-sm text-fg-tertiary">{product.kind}</p></CardHeader><CardFooter className="mt-auto items-center justify-between gap-3 pt-4"><span className="font-display text-lg font-semibold text-fg">{product.price}</span><Button size="sm">Buy now<ArrowUpRight className="size-3.5" aria-hidden="true" /></Button></CardFooter></Card>))}</div></div></section>);}
42 results in Courses and Templates
Course · 42 lessons
Asset pack · 60 presets
Template · instant access
Ebook · 180 pages
Replay · 3 hours
Audio · 120 loops