Marketplace Toolbar Product Grid

Dense product grid with an always-visible inline filter toolbar: search, a segmented category, a cascading subcategory select, and a searchable brand combobox, powered by the shared product-filters engine. Self-fetches from your API via productsUrl with an SSR-friendly first page, runs client-side on a dataset, or emits queries in controlled server mode.

PRO

Productlist3: Marketplace Toolbar Product Grid

A dense product grid with an always-visible inline filter toolbar at the top (no sidebar, no drawer). The demo toolbar holds a search box, a segmented category, a cascading subcategory select, and a searchable multi-brand combobox; below it a result count with removable active-filter chips and a sort dropdown, then a compact grid (up to 5 columns) with numbered pagination.

Filtering is powered by the shared product-filters engine, which installs automatically with the block.

Upgrade to Pro

Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.

Installation

Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.

Radix flavor

bash
npx shadcn add "https://ui.beste.co/r/productlist3?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://ui.beste.co/r-base/productlist3?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block, the engine (components/beste/component/product-filters.tsx), the individual filter controls, and the shadcn/ui dependencies they need.

Quick start

The installed file exports productlist3Demo alongside the block: the exact props behind the preview above. Spread it to get a working grid in one line.

tsx
import { Productlist3, productlist3Demo } from "@/components/beste/block/productlist3";

export default function Marketplace() {
  return <Productlist3 {...productlist3Demo} />;
}

Then replace the demo with your own props. The demo self-fetches from a hosted sample API; in your app, point productsUrl at your own endpoint (see Data modes) or pass a local products array.

tsx
import { Productlist3 } from "@/components/beste/block/productlist3";

export default function Marketplace() {
  return (
    <Productlist3
      heading="Marketplace"
      pageSize={15}
      productsUrl="/api/products"
      sortOptions={[
        { label: "Featured", value: "featured" },
        { label: "Price: Low to High", value: "price-asc" },
      ]}
      filterGroups={[
        { id: "q", label: "Search", type: "search", placeholder: "Search the catalog" },
        {
          id: "category",
          label: "Category",
          type: "segmented",
          options: [
            { label: "Shoes", value: "shoes" },
            { label: "Apparel", value: "apparel" },
          ],
        },
        {
          id: "brand",
          label: "Brand",
          type: "combobox",
          multiple: true,
          options: [{ label: "Acme", value: "acme" }],
        },
      ]}
    />
  );
}

The toolbar renders the search group stretched on the left; every other group sits inline and wraps. segmented keeps its natural width, other controls get a fixed width.

Props

PropTypeDefaultDescription
headingstringSection heading
descriptionstringSection intro text
productsUrlstringEndpoint the block fetches itself on every query (Data modes); products is ignored while set
initialProductsProductItem[]SSR-fetched first page; skips the initial client fetch
initialTotalCountnumberTotal matches for initialProducts
productsProductItem[][]Client mode dataset; ignored while productsUrl is set
filterGroupsProductFilterGroup[][]Toolbar filter definitions, rendered in order
sortOptionsProductSortOption[][]Sort dropdown entries ({ label, value }). Omit to hide the dropdown
defaultSortstringfirst sort optionInitially selected sort value
defaultFiltersRecord<string, string[]>{}Initial selections keyed by group id, e.g. { category: ["shoes"] }. Restore from the URL
pageSizenumber15Products per page
mode"client" | "server""client"See Data modes. productsUrl implies server behavior
totalCountnumberproducts.lengthServer mode: total matches across all pages, drives pagination
isLoadingbooleanfalseServer mode: shows the skeleton grid while fetching
onQueryChange(query: ProductQuery) => voidCalled on every filter, sort, and page action
classNamestringExtra classes for the outer <section>

ProductItem

ts
{
  title: string;
  href: string;
  brand?: string;
  price: number;
  compareAtPrice?: number;      // struck-through original price
  image: { src: string; alt: string };
  badge?: string;               // corner badge, e.g. "Sale"
  rating?: number;
  reviewCount?: number;
  keywords?: string[];          // extra terms matched by "search"
  attributes?: Record<string, string[]>; // filterable values keyed by group id
}

attributes keys must match your filterGroups ids, e.g. { category: ["sneakers"], color: ["black"] }. A product matches an option group when any of its attribute values is selected.

Filter groups

Each entry in filterGroups is a ProductFilterGroup:

FieldTypeDescription
idstringUnique key; option-based groups match product.attributes[id]
labelstringControl label
typeProductFilterTypeOne of the types below (default checkbox)
optionsProductFilterOption[]Required for option-based types
min / max / stepnumberBounds for slider (and placeholders for range)
unitstringPrefix for numeric values, e.g. "$"
placeholderstringSearch input / dropdown trigger placeholder
searchPlaceholderstringSearch box inside a combobox
clearLabelstringselect: label of the first item that clears the selection
multiplebooleancombobox: allow multiple selections
dependsOnstringCascading: parent group id (see below)
ts
type ProductFilterOption = {
  label: string;
  value: string;
  count?: number;
  swatch?: string;
  parentValue?: string;
  children?: ProductFilterOption[];
};

Filter types

typeControlSelectionMatched against
checkboxcheckbox listmultiattributes[id]
radioradio listsingle (click active to clear)attributes[id]
chipsbutton chipsmultiattributes[id]
segmentedsegmented tabssingle (click active to clear)attributes[id]
swatchcolor circlesmultiattributes[id]
toggleswitch rowsmultiattributes[id]
selectdropdownsingleattributes[id]
multiselectcheckbox dropdownmultiattributes[id]
comboboxsearchable dropdownsingle or multi (multiple)attributes[id]
treenested checkboxesmulti (parent covers subtree)attributes[id]
ratingstar rows ("n & up")singleproduct.rating (minimum)
pricecheckbox bucketsmultiproduct.price (option values are "min-max")
sliderrange slidersingle rangeproduct.price; needs min/max/step
rangemin/max number inputssingle range, open-endedproduct.price
searchtext inputtextproduct.title + product.keywords, every word must match

Cascading filters (dependsOn)

A group with dependsOn: "<parentId>" stays disabled until its parent has a selection, and its own selection clears whenever the parent changes.

tsx
{ id: "category", label: "Category", type: "select",
  options: [{ label: "Shoes", value: "shoes" }] },
{ id: "subcategory", label: "Subcategory", type: "select", dependsOn: "category",
  options: [{ label: "Sneakers", value: "sneakers", parentValue: "shoes" }] },

Filter components

Every control this block renders is an individual Beste UI filter component from the Filter category — browse, preview, and install any of them on their own there:

filter-checkbox, filter-radio, filter-chips, filter-segmented, filter-swatch, filter-toggle, filter-select, filter-multiselect, filter-combobox, filter-tree, filter-rating, filter-slider, filter-range, filter-search.

They are wired together by the hidden product-filters engine that ships with this block, so a single install pulls in every control plus the state, debounce, cascading, and query logic. To build your own layout, use the engine directly: useProductFilters + ProductFilterField (renders the right control per group) + ProductFilterChips, with matchesProductFilters and sortProducts backing client mode.

Data modes

Supply products in one of three ways:

  1. Client — pass the full products array; filtering, sorting, and pagination run locally.
  2. Remote (productsUrl) — the block fetches each page itself. The endpoint receives ?filters=<json>&sort=&page=&pageSize= and must return { products, totalCount }. Pair with initialProducts/initialTotalCount for an SSR-rendered first page (fetch page 1 in a server component, pass it down, and only later interactions hit the network from the client).
  3. Server (mode: "server") — you own the fetching (custom caching, URL sync). Every action calls onQueryChange; you fetch and feed products + totalCount back through props.

ProductQuery

What onQueryChange and the productsUrl request encode:

ts
{
  filters: Record<string, string[]>; // selections per group id
  sort: string;                      // current sort value (passed through verbatim in server mode)
  page: number;                      // 1-based
  pageSize: number;
}

Filter value shapes inside filters:

Behavior notes

Wiring the form up

This block ships the form markup only; state, validation, and submit are yours to add. Our guide wires the shadcn Field primitives to React Hook Form, TanStack Form, and Formisch on one field system.

More Product List blocks

View all Product List
PRO

productlist1

Filterable Product Grid

Product listing with sidebar filters, sorting, pagination, and active filter chips, powered by the shared product-filters engine. Fifteen filter types including checkboxes, chips, color swatches, price sliders, rating, search, dropdowns, and cascading selects. Runs fully client-side on a given dataset, self-fetches from your API via productsUrl with an SSR-friendly first page, or emits queries in controlled server mode.

PRO

productlist2

Product Grid With Filter Drawer

Product catalog with a single Filters button that opens a side drawer: keyword search, cascading category and subcategory selects, a searchable multi-brand combobox, a size multiselect, and a segmented gender control, powered by the shared product-filters engine. Runs fully client-side on a given dataset, self-fetches from your API via productsUrl with an SSR-friendly first page, or emits queries in controlled server mode.

PRO

productlist4

Quick-Shop Product Grid

Product grid with a left filter sidebar where hovering a card reveals a quick-shop tray with sizes and an add-to-cart button, plus a category tag and sale badge, powered by the shared product-filters engine. Self-fetches from your API via productsUrl with an SSR-friendly first page, runs client-side on a dataset, or emits queries in controlled server mode.

PRO

productlist5

Load More Feed With Category Pills

Three-column product feed led by a row of category pills, with a search and sort, and a Load more button plus progress bar that appends the next batch instead of paginating, powered by the shared product-filters engine. Self-fetches from your API via productsUrl with an SSR-friendly first page, runs client-side on a dataset, or emits queries in controlled server mode.