Sana Assistant (online)
Table of Contents

Implementing a custom facet presentation

From this article you will learn how to create a custom list facet presentation that renders facet values as selectable chips.

Please use the following reference articles for more details on the extension infrastructure:

Implementation

Start with a new project

Create a new add-on project named "Sana.Extensions.CustomFacetPresentations" as described in the add-on development tutorial.

The "CustomFacetPresentations" constant is the name used in this tutorial, but in real add-ons it should be replaced by the name of the add-on.

Create the server-side extension class

Create a class ChipListFacetDisplayExtension inherited from ProductListFacetDisplayExtension:

using Sana.Extensions.Search;

namespace Sana.Extensions.CustomFacetPresentations;

public class ChipListFacetDisplayExtension : ProductListFacetDisplayExtension
{
    public override string Type => "ChipList";

    public override string Title => "Chip list";
}

The Type value is the unique identifier used in the client export and in Sana Admin. The Title is shown to admin users when they choose a presentation for a facet.

To register a hidden presentation that keeps the facet in page data without rendering UI, override Hidden:

public class HiddenChipListFacetDisplayExtension : ProductListFacetDisplayExtension
{
    public override string Type => "HiddenChipList";

    public override string Title => "Hidden chip list";

    public override bool Hidden => true;
}

For range facets, inherit from ProductRangeFacetDisplayExtension instead. See ProductRangeFacetDisplayExtension reference.

Implement the inline list facet display component

Create ClientApp/webstore/components/facetDisplays/ChipListFacetDisplay.tsx:

import type { FacetListDisplay } from 'sana/facets';
import { Link } from 'sana/elements';

const ChipListFacetDisplay: FacetListDisplay = ({
  id,
  facet,
  values,
  valuesTotalCount,
  loadAllValues,
}) => {
  const hasValuesToLoad = valuesTotalCount > values.length;

  return (
    <div>
      <ul id={`${id}_chips`} aria-label={facet.name}>
        {values.map(value => (
          <li key={value.value ?? value.title}>
            <Link
              url={value.link.url}
              to={value.link.to}
              omitScroll
            >
              {value.title} ({value.count})
            </Link>
          </li>
        ))}
      </ul>
      {hasValuesToLoad &&
        <button type="button" onClick={loadAllValues}>
          Show all ({valuesTotalCount})
        </button>
      }
    </div>
  );
};

export default ChipListFacetDisplay;

Use the link property on each ListFacetValue with the Link component from sana/elements to navigate and apply the facet filter. Call loadAllValues when the initial subset of values is smaller than valuesTotalCount.

Implement the mobile modal list facet display component

Create ClientApp/webstore/components/facetDisplays/ChipListFacetModalDisplay.tsx:

import type { FacetListModalDisplay } from 'sana/facets';

const ChipListFacetModalDisplay: FacetListModalDisplay = ({
  id,
  facet,
  values,
  valuesTotalCount,
  toggleValue,
}) => {
  return (
    <div>
      <ul id={`${id}_chips`} aria-label={facet.name}>
        {values.map(value => (
          <li key={value.value ?? value.title}>
            <button
              type="button"
              aria-pressed={value.selected}
              onClick={() => toggleValue(value)}
            >
              {value.title} ({value.count})
            </button>
          </li>
        ))}
      </ul>
      {valuesTotalCount > values.length &&
        <p>{values.length} of {valuesTotalCount} values shown</p>
      }
    </div>
  );
};

export default ChipListFacetModalDisplay;

In the mobile facets modal, facet values do not include navigation links. Use toggleValue to stage selection locally; the shopper applies the filter when they confirm the modal.

Register the display components in the add-on export

Update ClientApp/webstore/index.ts to export the facet presentations:

import type { AddonExports } from 'sana/types';
import ChipListFacetDisplay from 'components/facetDisplays/ChipListFacetDisplay';
import ChipListFacetModalDisplay from 'components/facetDisplays/ChipListFacetModalDisplay';

const addonExports: AddonExports = {
  facets: {
    list: {
      ChipList: {
        display: ChipListFacetDisplay,
        modalDisplay: ChipListFacetModalDisplay,
      },
    },
  },
};

export default addonExports;

The key ChipList must match the Type property of ChipListFacetDisplayExtension.

Configure the facet in Sana Admin

After deploying the add-on:

  1. Open the product search index configuration in Sana Admin.
  2. Edit or add a list facet.
  3. In the display type selector, choose Chip list (the Title from the extension class).
  4. Save the search index and re-index if required.
  5. On a product list page that uses the facet, verify the custom chip UI appears on desktop and in the mobile facets modal.

The same presentation can be assigned to facets on individual product list pages through page settings.

Range facet presentations

Range facet presentations follow the same pattern with ProductRangeFacetDisplayExtension and FacetRangeDisplay / FacetRangeModalDisplay components.

The inline range display receives getTotalCount to preview how many products match a selected range before applying the filter, and onSubmit to apply the range. The modal display uses selectRange and onRangeChange to manage local selection state.

See Facet presentations (ADK) for the full range facet component contract.

See also