1. Search results

The search results widget template with layout options

The search results widget template, described in this topic, displays search results with a toggle to switch layout from a grid to a stack. Typically the widget takes over the entire page. The widget can display filters, facets, pagination controls, and sorting options.

Screenshot of a search results widget with layout options.

The following sections describe the search results widget template with layout options, and contains an API reference, and instructions on how to create code blocks that you can add to the WidgetsProvider in your React project using Search JS SDK for React. The widget uses the search results query hook to request and retrieve data.

To create a widget component, you can choose to use Search UI components CLI, a command line interface, or copy and paste code blocks manually.

Generate or create the component

This component is available in the plain or unstyled, styled, CSS-styled, and CSS module-styled variants. The following sections are instructions to generate or manually create the component in Typescript and JavaScript.

Important

Remember to adjust the entity model in the widget template to match your implementation.

Plain component

Plain widget templates are unstyled and ready to be customized with CSS styles or a theme. You have complete control over the design and content of the component.

TS CLI

With the Sitecore Search CLI you can replicate all necessary files of the widget template in your React project. The following procedure includes instructions for installing the CLI in your project and specifying the location where widget components are created.

If you have previously installed the CLI in your project and have a location defined in a file called .sc-search-settings.json, you can proceed directly to step 3.

To install the UI Components CLI in your project:

  1. To install the Search UI Components CLI, open the terminal of your IDE or command prompt and run the following command:

    npx install --save-dev @sitecore-search/cli
  2. To define the location of where widget components will be created, at the root of your project, in a file called .sc-search-settings.json, specify the location as shown in the following code block.

    {
        "components-path": "src-test/components"
    }
  3. To create the widget component, go to the location you defined in the .sc-search-settings.json file then open the terminal of your IDE or command prompt and run the following command:

    npx sc-search new-widget --language typescript --template search-results-row-grid --entity content --styling plain

TS manual

When you manually create widget templates in your React project, you need to ensure that you install any required dependencies and create all necessary files.

if you have previously installed the required NPM packages, you can skip the dependency installation steps.

To create the widget component using this widget template in your project:

  1. In your project, to install the development dependency, open the terminal of your IDE or command prompt and run the following command:

    npm install @radix-ui/react-icons@^1.3.0
  2. In a folder for the widget component, create a file called index.tsx then in the file, paste following code block.

    // index.tsx
    import { useState } from 'react';
    
    import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, GridIcon, ListBulletIcon } from '@radix-ui/react-icons';
    import { Presence } from '@radix-ui/react-presence';
    import type { SearchResultsInitialState, SearchResultsStoreState } from '@sitecore-search/react';
    import { WidgetDataType, useSearchResults, useSearchResultsSelectedFilters, widget } from '@sitecore-search/react';
    import {
      AccordionFacets,
      ArticleCard,
      CardViewSwitcher,
      FacetItem,
      Pagination,
      SearchResultsAccordionFacets,
      Select,
      SortSelect,
    } from '@sitecore-search/ui';
    
    type ArticleModel = {
      id: string;
      type?: string;
      title?: string;
      name?: string;
      subtitle?: string;
      url?: string;
      description?: string;
      content_text?: string;
      image_url?: string;
      source_id?: string;
    };
    type ArticlesSearchResultsProps = {
      defaultSortType?: SearchResultsStoreState['sortType'];
      defaultPage?: SearchResultsStoreState['page'];
      defaultItemsPerPage?: SearchResultsStoreState['itemsPerPage'];
      defaultKeyphrase?: SearchResultsStoreState['keyphrase'];
    };
    type InitialState = SearchResultsInitialState<'itemsPerPage' | 'keyphrase' | 'page' | 'sortType'>;
    const buildRangeLabel = (min: number | undefined, max: number | undefined): string => {
      return typeof min === 'undefined' ? `< $${max}` : typeof max === 'undefined' ? ` > $${min}` : `$${min} - $${max}`;
    };
    const buildFacetLabel = (selectedFacet: any) => {
      if ('min' in selectedFacet || 'max' in selectedFacet) {
        return `${selectedFacet.facetLabel}: ${buildRangeLabel(selectedFacet.min, selectedFacet.max)}`;
      }
      return `${selectedFacet.facetLabel}: ${selectedFacet.valueLabel}`;
    };
    export const SearchResultsWithLayoutOptionComponent = ({
      defaultSortType = 'featured_desc',
      defaultPage = 1,
      defaultKeyphrase = '',
      defaultItemsPerPage = 24,
    }: ArticlesSearchResultsProps) => {
      const {
        widgetRef,
        actions: {
          onResultsPerPageChange,
          onPageNumberChange,
          onItemClick,
          onRemoveFilter,
          onSortChange,
          onFacetClick,
          onClearFilters,
        },
        state: { sortType, page, itemsPerPage },
        queryResult: {
          isLoading,
          isFetching,
          data: {
            total_item: totalItems = 0,
            sort: { choices: sortChoices = [] } = {},
            facet: facets = [],
            content: articles = [],
          } = {},
        },
      } = useSearchResults<ArticleModel, InitialState>({
        state: {
          sortType: defaultSortType,
          page: defaultPage,
          itemsPerPage: defaultItemsPerPage,
          keyphrase: defaultKeyphrase,
        },
      });
      const totalPages = Math.ceil(totalItems / itemsPerPage);
      const selectedSortIndex = sortChoices.findIndex((s) => s.name === sortType);
      const selectedFacetsFromApi = useSearchResultsSelectedFilters();
      const defaultCardView = CardViewSwitcher.CARD_VIEW_LIST;
      const [dir, setDir] = useState(defaultCardView);
      const onToggle = (value = defaultCardView) => setDir(value);
      return (
        <>
          {isLoading && (
            <div>
              <Presence present={isLoading}>
                <svg
                  aria-busy={isLoading}
                  aria-hidden={!isLoading}
                  focusable="false"
                  role="progressbar"
                  viewBox="0 0 20 20"
                >
                  <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                </svg>
              </Presence>
            </div>
          )}
          {!isLoading && (
            <div ref={widgetRef}>
              <div>
                {isFetching && (
                  <div>
                    <Presence present={true}>
                      <svg aria-busy={true} aria-hidden={false} focusable="false" role="progressbar" viewBox="0 0 20 20">
                        <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                      </svg>
                    </Presence>
                  </div>
                )}
                {totalItems > 0 && (
                  <>
                    <section>
                      {selectedFacetsFromApi.length > 0 && <button onClick={onClearFilters}>Clear Filters</button>}
                      <ul>
                        {selectedFacetsFromApi.map((selectedFacet) => (
                          <li key={`${selectedFacet.facetId}${selectedFacet.facetLabel}${selectedFacet.valueLabel}`}>
                            <span>{buildFacetLabel(selectedFacet)}</span>
                            <button onClick={() => onRemoveFilter(selectedFacet)}>X</button>
                          </li>
                        ))}
                      </ul>
                      <SearchResultsAccordionFacets
                        defaultFacetTypesExpandedList={[]}
                        onFacetTypesExpandedListChange={() => {}}
                        onFacetValueClick={onFacetClick}
                      >
                        {facets.map((f) => (
                          <AccordionFacets.Facet facetId={f.name} key={f.name}>
                            <AccordionFacets.Header>
                              <AccordionFacets.Trigger>{f.label}</AccordionFacets.Trigger>
                            </AccordionFacets.Header>
                            <AccordionFacets.Content>
                              <AccordionFacets.ValueList>
                                {f.value.map((v, index) => (
                                  <FacetItem
                                    {...{
                                      index,
                                      facetValueId: v.id,
                                    }}
                                    key={v.id}
                                  >
                                    <AccordionFacets.ItemCheckbox>
                                      <AccordionFacets.ItemCheckboxIndicator>
                                        <CheckIcon />
                                      </AccordionFacets.ItemCheckboxIndicator>
                                    </AccordionFacets.ItemCheckbox>
                                    <AccordionFacets.ItemLabel>
                                      {v.text} {v.count && `(${v.count})`}
                                    </AccordionFacets.ItemLabel>
                                  </FacetItem>
                                ))}
                              </AccordionFacets.ValueList>
                            </AccordionFacets.Content>
                          </AccordionFacets.Facet>
                        ))}
                      </SearchResultsAccordionFacets>
                    </section>
                    <section>
                      <section>
                        {totalItems && (
                          <div>
                            <b>
                              Showing {itemsPerPage * (page - 1) + 1} - {itemsPerPage * (page - 1) + articles.length} of{' '}
                              {totalItems} results
                            </b>
                          </div>
                        )}
    
                        <div>
                          {/* Card View Switcher */}
                          <CardViewSwitcher.Root onValueChange={onToggle} defaultValue={defaultCardView}>
                            <CardViewSwitcher.Item value="grid" aria-label="Grid View">
                              <GridIcon />
                            </CardViewSwitcher.Item>
                            <CardViewSwitcher.Item value="list" aria-label="List View">
                              <ListBulletIcon />
                            </CardViewSwitcher.Item>
                          </CardViewSwitcher.Root>
    
                          {/* Sort Select */}
                          <SortSelect.Root defaultValue={sortChoices[selectedSortIndex]?.name} onValueChange={onSortChange}>
                            <SortSelect.Trigger>
                              <SortSelect.SelectValue>
                                {selectedSortIndex > -1 ? sortChoices[selectedSortIndex].label : ''}
                              </SortSelect.SelectValue>
                              <SortSelect.Icon />
                            </SortSelect.Trigger>
                            <SortSelect.Content>
                              <SortSelect.Viewport>
                                {sortChoices.map((option) => (
                                  <SortSelect.Option value={option} key={option.name}>
                                    <SortSelect.OptionText>{option.label}</SortSelect.OptionText>
                                  </SortSelect.Option>
                                ))}
                              </SortSelect.Viewport>
                            </SortSelect.Content>
                          </SortSelect.Root>
                        </div>
                      </section>
    
                      {/* Results */}
                      {dir === CardViewSwitcher.CARD_VIEW_GRID ? (
                        <div>
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id}>
                              <div>
                                <ArticleCard.Image src={a.image_url} />
                              </div>
                              <ArticleCard.Id>{a.id}</ArticleCard.Id>
                              <ArticleCard.Title>
                                <a
                                  href="#"
                                  onClick={(e) => {
                                    e.preventDefault();
                                    onItemClick({
                                      id: a.id,
                                      index,
                                      sourceId: a.source_id,
                                    });
                                  }}
                                >
                                  {a.name}
                                </a>
                              </ArticleCard.Title>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      ) : (
                        <div>
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id}>
                              <div>
                                <ArticleCard.Image src={a.image_url} />
                              </div>
                              <div>
                                <ArticleCard.Title>
                                  <a
                                    href="#"
                                    onClick={(e) => {
                                      e.preventDefault();
                                      onItemClick({
                                        id: a.id,
                                        index,
                                        sourceId: a.source_id,
                                      });
                                    }}
                                  >
                                    {a.name}
                                  </a>
                                </ArticleCard.Title>
                                <ArticleCard.Content>{a.content_text}</ArticleCard.Content>
                              </div>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      )}
                      <div>
                        <div>
                          <label>Results Per Page</label>
                          <Select.Root
                            defaultValue={String(defaultItemsPerPage)}
                            onValueChange={(v) =>
                              onResultsPerPageChange({
                                numItems: Number(v),
                              })
                            }
                          >
                            <Select.Trigger>
                              <Select.SelectValue />
                              <Select.Icon />
                            </Select.Trigger>
                            <Select.SelectContent>
                              <Select.Viewport>
                                <Select.SelectItem value="24">
                                  <SortSelect.OptionText>24</SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="48">
                                  <SortSelect.OptionText>48</SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="64">
                                  <SortSelect.OptionText>64</SortSelect.OptionText>
                                </Select.SelectItem>
                              </Select.Viewport>
                            </Select.SelectContent>
                          </Select.Root>
                        </div>
                        <Pagination.Root
                          currentPage={page}
                          defaultCurrentPage={1}
                          totalPages={totalPages}
                          onPageChange={(v) =>
                            onPageNumberChange({
                              page: v,
                            })
                          }
                        >
                          <Pagination.PrevPage onClick={(e) => e.preventDefault()}>
                            <ArrowLeftIcon />
                          </Pagination.PrevPage>
                          <Pagination.Pages>
                            {(pagination) =>
                              Pagination.paginationLayout(pagination, {}).map(({ page, type }) =>
                                type === 'page' ? (
                                  <Pagination.Page
                                    key={page}
                                    aria-label={`Page ${page}`}
                                    page={page as number}
                                    onClick={(e) => e.preventDefault()}
                                  >
                                    {page}
                                  </Pagination.Page>
                                ) : (
                                  <span key={type}>...</span>
                                ),
                              )
                            }
                          </Pagination.Pages>
                          <Pagination.NextPage onClick={(e) => e.preventDefault()}>
                            <ArrowRightIcon />
                          </Pagination.NextPage>
                        </Pagination.Root>
                      </div>
                    </section>
                  </>
                )}
                {totalItems <= 0 && !isFetching && (
                  <div>
                    <h3>0 Results</h3>
                  </div>
                )}
              </div>
            </div>
          )}
        </>
      );
    };
    const SearchResultsWithLayoutOptionWidget = widget(SearchResultsWithLayoutOptionComponent, WidgetDataType.SEARCH_RESULTS, 'content');
    export default SearchResultsWithLayoutOptionWidget;

JS CLI

With the Sitecore Search CLI you can replicate all necessary files of the widget template in your React project. The following procedure includes instructions for installing the CLI in your project and specifying the location where widget components are created.

If you have previously installed the CLI in your project and have a location defined in a file called .sc-search-settings.json, you can proceed directly to step 3.

To install the UI Components CLI in your project:

  1. To install the Search UI Components CLI, open the terminal of your IDE or command prompt and run the following command:

    npx install --save-dev @sitecore-search/cli
  2. To define the location of where widget components will be created, at the root of your project, in a file called .sc-search-settings.json, specify the location as shown in the following code block.

    {
        "components-path": "src-test/components"
    }
  3. At the location listed in .sc-search-settings.json, to create the widget component in an index.jsx file, paste the following command in the terminal of your IDE.

    npx sc-search new-widget --language javascript --template search-results-row-grid --entity content --styling plain

JS manual

When you manually create widget templates in your React project, you need to ensure that you install any required dependencies and create all necessary files.

if you have previously installed the required NPM packages, you can skip the dependency installation steps.

To create the widget component using this widget template in your project:

  1. In your project, to install the development dependency, open the terminal of your IDE or command prompt and run the following command:

    npm install @radix-ui/react-icons@^1.3.0
  2. In a folder for the widget component, create a file called index.jsx then in the file, paste following code block.

    // index.jsx
    import { useState } from 'react';
    
    import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, GridIcon, ListBulletIcon } from '@radix-ui/react-icons';
    import { Presence } from '@radix-ui/react-presence';
    import { WidgetDataType, useSearchResults, useSearchResultsSelectedFilters, widget } from '@sitecore-search/react';
    import {
      AccordionFacets,
      ArticleCard,
      CardViewSwitcher,
      FacetItem,
      Pagination,
      SearchResultsAccordionFacets,
      Select,
      SortSelect,
    } from '@sitecore-search/ui';
    
    const buildRangeLabel = (min, max) => {
      return typeof min === 'undefined' ? `< $${max}` : typeof max === 'undefined' ? ` > $${min}` : `$${min} - $${max}`;
    };
    const buildFacetLabel = (selectedFacet) => {
      if ('min' in selectedFacet || 'max' in selectedFacet) {
        return `${selectedFacet.facetLabel}: ${buildRangeLabel(selectedFacet.min, selectedFacet.max)}`;
      }
      return `${selectedFacet.facetLabel}: ${selectedFacet.valueLabel}`;
    };
    export const SearchResultsWithLayoutOptionComponent = ({
      defaultSortType = 'featured_desc',
      defaultPage = 1,
      defaultKeyphrase = '',
      defaultItemsPerPage = 24,
    }) => {
      const {
        widgetRef,
        actions: {
          onResultsPerPageChange,
          onPageNumberChange,
          onItemClick,
          onRemoveFilter,
          onSortChange,
          onFacetClick,
          onClearFilters,
        },
        state: { sortType, page, itemsPerPage },
        queryResult: {
          isLoading,
          isFetching,
          data: {
            total_item: totalItems = 0,
            sort: { choices: sortChoices = [] } = {},
            facet: facets = [],
            content: articles = [],
          } = {},
        },
      } = useSearchResults({
        state: {
          sortType: defaultSortType,
          page: defaultPage,
          itemsPerPage: defaultItemsPerPage,
          keyphrase: defaultKeyphrase,
        },
      });
      const totalPages = Math.ceil(totalItems / itemsPerPage);
      const selectedSortIndex = sortChoices.findIndex((s) => s.name === sortType);
      const selectedFacetsFromApi = useSearchResultsSelectedFilters();
      const defaultCardView = CardViewSwitcher.CARD_VIEW_LIST;
      const [dir, setDir] = useState(defaultCardView);
      const onToggle = (value = defaultCardView) => setDir(value);
      return (
        <>
          {isLoading && (
            <div>
              <Presence present={isLoading}>
                <svg
                  aria-busy={isLoading}
                  aria-hidden={!isLoading}
                  focusable="false"
                  role="progressbar"
                  viewBox="0 0 20 20"
                >
                  <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                </svg>
              </Presence>
            </div>
          )}
          {!isLoading && (
            <div ref={widgetRef}>
              <div>
                {isFetching && (
                  <div>
                    <Presence present={true}>
                      <svg aria-busy={true} aria-hidden={false} focusable="false" role="progressbar" viewBox="0 0 20 20">
                        <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                      </svg>
                    </Presence>
                  </div>
                )}
                {totalItems > 0 && (
                  <>
                    <section>
                      {selectedFacetsFromApi.length > 0 && <button onClick={onClearFilters}>Clear Filters</button>}
                      <ul>
                        {selectedFacetsFromApi.map((selectedFacet) => (
                          <li key={`${selectedFacet.facetId}${selectedFacet.facetLabel}${selectedFacet.valueLabel}`}>
                            <span>{buildFacetLabel(selectedFacet)}</span>
                            <button onClick={() => onRemoveFilter(selectedFacet)}>X</button>
                          </li>
                        ))}
                      </ul>
                      <SearchResultsAccordionFacets
                        defaultFacetTypesExpandedList={[]}
                        onFacetTypesExpandedListChange={() => {}}
                        onFacetValueClick={onFacetClick}
                      >
                        {facets.map((f) => (
                          <AccordionFacets.Facet facetId={f.name} key={f.name}>
                            <AccordionFacets.Header>
                              <AccordionFacets.Trigger>{f.label}</AccordionFacets.Trigger>
                            </AccordionFacets.Header>
                            <AccordionFacets.Content>
                              <AccordionFacets.ValueList>
                                {f.value.map((v, index) => (
                                  <FacetItem
                                    {...{
                                      index,
                                      facetValueId: v.id,
                                    }}
                                    key={v.id}
                                  >
                                    <AccordionFacets.ItemCheckbox>
                                      <AccordionFacets.ItemCheckboxIndicator>
                                        <CheckIcon />
                                      </AccordionFacets.ItemCheckboxIndicator>
                                    </AccordionFacets.ItemCheckbox>
                                    <AccordionFacets.ItemLabel>
                                      {v.text} {v.count && `(${v.count})`}
                                    </AccordionFacets.ItemLabel>
                                  </FacetItem>
                                ))}
                              </AccordionFacets.ValueList>
                            </AccordionFacets.Content>
                          </AccordionFacets.Facet>
                        ))}
                      </SearchResultsAccordionFacets>
                    </section>
                    <section>
                      <section>
                        {totalItems && (
                          <div>
                            <b>
                              Showing {itemsPerPage * (page - 1) + 1} - {itemsPerPage * (page - 1) + articles.length} of{' '}
                              {totalItems} results
                            </b>
                          </div>
                        )}
    
                        <div>
                          {/* Card View Switcher */}
                          <CardViewSwitcher.Root onValueChange={onToggle} defaultValue={defaultCardView}>
                            <CardViewSwitcher.Item value="grid" aria-label="Grid View">
                              <GridIcon />
                            </CardViewSwitcher.Item>
                            <CardViewSwitcher.Item value="list" aria-label="List View">
                              <ListBulletIcon />
                            </CardViewSwitcher.Item>
                          </CardViewSwitcher.Root>
    
                          {/* Sort Select */}
                          <SortSelect.Root defaultValue={sortChoices[selectedSortIndex]?.name} onValueChange={onSortChange}>
                            <SortSelect.Trigger>
                              <SortSelect.SelectValue>
                                {selectedSortIndex > -1 ? sortChoices[selectedSortIndex].label : ''}
                              </SortSelect.SelectValue>
                              <SortSelect.Icon />
                            </SortSelect.Trigger>
                            <SortSelect.Content>
                              <SortSelect.Viewport>
                                {sortChoices.map((option) => (
                                  <SortSelect.Option value={option} key={option.name}>
                                    <SortSelect.OptionText>{option.label}</SortSelect.OptionText>
                                  </SortSelect.Option>
                                ))}
                              </SortSelect.Viewport>
                            </SortSelect.Content>
                          </SortSelect.Root>
                        </div>
                      </section>
    
                      {/* Results */}
                      {dir === CardViewSwitcher.CARD_VIEW_GRID ? (
                        <div>
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id}>
                              <div>
                                <ArticleCard.Image src={a.image_url} />
                              </div>
                              <ArticleCard.Id>{a.id}</ArticleCard.Id>
                              <ArticleCard.Title>
                                <a
                                  href="#"
                                  onClick={(e) => {
                                    e.preventDefault();
                                    onItemClick({
                                      id: a.id,
                                      index,
                                      sourceId: a.source_id,
                                    });
                                  }}
                                >
                                  {a.name}
                                </a>
                              </ArticleCard.Title>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      ) : (
                        <div>
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id}>
                              <div>
                                <ArticleCard.Image src={a.image_url} />
                              </div>
                              <div>
                                <ArticleCard.Title>
                                  <a
                                    href="#"
                                    onClick={(e) => {
                                      e.preventDefault();
                                      onItemClick({
                                        id: a.id,
                                        index,
                                        sourceId: a.source_id,
                                      });
                                    }}
                                  >
                                    {a.name}
                                  </a>
                                </ArticleCard.Title>
                                <ArticleCard.Content>{a.content_text}</ArticleCard.Content>
                              </div>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      )}
                      <div>
                        <div>
                          <label>Results Per Page</label>
                          <Select.Root
                            defaultValue={String(defaultItemsPerPage)}
                            onValueChange={(v) =>
                              onResultsPerPageChange({
                                numItems: Number(v),
                              })
                            }
                          >
                            <Select.Trigger>
                              <Select.SelectValue />
                              <Select.Icon />
                            </Select.Trigger>
                            <Select.SelectContent>
                              <Select.Viewport>
                                <Select.SelectItem value="24">
                                  <SortSelect.OptionText>24</SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="48">
                                  <SortSelect.OptionText>48</SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="64">
                                  <SortSelect.OptionText>64</SortSelect.OptionText>
                                </Select.SelectItem>
                              </Select.Viewport>
                            </Select.SelectContent>
                          </Select.Root>
                        </div>
                        <Pagination.Root
                          currentPage={page}
                          defaultCurrentPage={1}
                          totalPages={totalPages}
                          onPageChange={(v) =>
                            onPageNumberChange({
                              page: v,
                            })
                          }
                        >
                          <Pagination.PrevPage onClick={(e) => e.preventDefault()}>
                            <ArrowLeftIcon />
                          </Pagination.PrevPage>
                          <Pagination.Pages>
                            {(pagination) =>
                              Pagination.paginationLayout(pagination, {}).map(({ page, type }) =>
                                type === 'page' ? (
                                  <Pagination.Page
                                    key={page}
                                    aria-label={`Page ${page}`}
                                    page={page}
                                    onClick={(e) => e.preventDefault()}
                                  >
                                    {page}
                                  </Pagination.Page>
                                ) : (
                                  <span key={type}>...</span>
                                ),
                              )
                            }
                          </Pagination.Pages>
                          <Pagination.NextPage onClick={(e) => e.preventDefault()}>
                            <ArrowRightIcon />
                          </Pagination.NextPage>
                        </Pagination.Root>
                      </div>
                    </section>
                  </>
                )}
                {totalItems <= 0 && !isFetching && (
                  <div>
                    <h3>0 Results</h3>
                  </div>
                )}
              </div>
            </div>
          )}
        </>
      );
    };
    const SearchResultsWithLayoutOptionWidget = widget(SearchResultsWithLayoutOptionComponent, WidgetDataType.SEARCH_RESULTS, 'content');
    export default SearchResultsWithLayoutOptionWidget;

Styled component

Styled widget templates are built with styled UI primitives. To customize them, you need to edit the styled component. You can control the design and content of the component in the styled file.

TS CLI

With the Sitecore Search CLI you can replicate all necessary files of the widget template in your React project. The following procedure includes instructions for installing the CLI in your project and specifying the location where widget components are created.

If you have previously installed the CLI in your project and have a location defined in a file called .sc-search-settings.json, you can proceed directly to step 3.

To install the UI Components CLI in your project:

  1. To install the Search UI Components CLI, open the terminal of your IDE or command prompt and run the following command:

    npx install --save-dev @sitecore-search/cli
  2. To define the location of where widget components will be created, at the root of your project, in a file called .sc-search-settings.json, specify the location as shown in the following code block.

    {
        "components-path": "src-test/components"
    }
  3. To create the widget component in index.tsx and styled.ts files, go to the location you defined in the .sc-search-settings.json file then open the terminal of your IDE or command prompt and run the following command:

    npx sc-search new-widget --language typescript --template search-results-row-grid --entity content --styling styled

TS manual

When you manually create widget templates in your React project, you need to ensure that you install any required dependencies and create all necessary files.

if you have previously installed the required NPM packages, you can skip the dependency installation steps.

To create the widget component using this widget template in your project:

  1. In your project, to install development dependencies, open the terminal of your IDE or command prompt and run the following command:

    npm install styled-components@^6.1.0 @radix-ui/react-icons@^1.3.0
  2. In your project, to install the development dependency, open the terminal of your IDE or command prompt and run the following command:

    npm install --save-dev @types/styled-components@^5.1.29
  3. In a folder for the widget component, create files called index.tsx and styled.ts then in respective files, paste following code blocks.

    // index.tsx
    import { useState } from 'react';
    
    import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, GridIcon, ListBulletIcon } from '@radix-ui/react-icons';
    import { Presence } from '@radix-ui/react-presence';
    import type { SearchResultsInitialState, SearchResultsStoreState } from '@sitecore-search/react';
    import { WidgetDataType, useSearchResults, useSearchResultsSelectedFilters, widget } from '@sitecore-search/react';
    import { AccordionFacets, CardViewSwitcher, Pagination, Select, SortSelect } from '@sitecore-search/ui';
    
    import {
      AccordionFacetsStyled,
      ArticleCardRowStyled,
      ArticleCardStyled,
      CardViewSwitcherStyled,
      FiltersStyled,
      GridStyled,
      LoaderAnimation,
      LoaderContainer,
      PageControlsStyled,
      PaginationStyled,
      QuerySummaryStyled,
      RowStyled,
      SearchResultsLayout,
      SelectStyled,
      SortSelectStyled,
    } from './styled';
    
    type ArticleModel = {
      id: string;
      type?: string;
      title?: string;
      name?: string;
      subtitle?: string;
      url?: string;
      description?: string;
      content_text?: string;
      image_url?: string;
      source_id?: string;
    };
    
    type ArticlesSearchResultsProps = {
      defaultSortType?: SearchResultsStoreState['sortType'];
      defaultPage?: SearchResultsStoreState['page'];
      defaultItemsPerPage?: SearchResultsStoreState['itemsPerPage'];
      defaultKeyphrase?: SearchResultsStoreState['keyphrase'];
    };
    
    type InitialState = SearchResultsInitialState<'itemsPerPage' | 'keyphrase' | 'page' | 'sortType'>;
    
    const buildRangeLabel = (min: number | undefined, max: number | undefined): string => {
      return typeof min === 'undefined' ? `< $${max}` : typeof max === 'undefined' ? ` > $${min}` : `$${min} - $${max}`;
    };
    
    const buildFacetLabel = (selectedFacet: any) => {
      if ('min' in selectedFacet || 'max' in selectedFacet) {
        return `${selectedFacet.facetLabel}: ${buildRangeLabel(selectedFacet.min, selectedFacet.max)}`;
      }
      return `${selectedFacet.facetLabel}: ${selectedFacet.valueLabel}`;
    };
    
    export const SearchResultsWithLayoutOptionComponent = ({
      defaultSortType = 'featured_desc',
      defaultPage = 1,
      defaultKeyphrase = '',
      defaultItemsPerPage = 24,
    }: ArticlesSearchResultsProps) => {
      const {
        widgetRef,
        actions: {
          onResultsPerPageChange,
          onPageNumberChange,
          onItemClick,
          onRemoveFilter,
          onSortChange,
          onFacetClick,
          onClearFilters,
        },
        state: { sortType, page, itemsPerPage },
        queryResult: {
          isLoading,
          isFetching,
          data: {
            total_item: totalItems = 0,
            sort: { choices: sortChoices = [] } = {},
            facet: facets = [],
            content: articles = [],
          } = {},
        },
      } = useSearchResults<ArticleModel, InitialState>({
        state: {
          sortType: defaultSortType,
          page: defaultPage,
          itemsPerPage: defaultItemsPerPage,
          keyphrase: defaultKeyphrase,
        },
      });
      const totalPages = Math.ceil(totalItems / itemsPerPage);
      const selectedSortIndex = sortChoices.findIndex((s) => s.name === sortType);
      const selectedFacetsFromApi = useSearchResultsSelectedFilters();
      const defaultCardView = CardViewSwitcher.CARD_VIEW_LIST;
      const [dir, setDir] = useState(defaultCardView);
      const onToggle = (value = defaultCardView) => setDir(value);
    
      return (
        <>
          {isLoading && (
            <LoaderContainer>
              <Presence present={isLoading}>
                <LoaderAnimation
                  aria-busy={isLoading}
                  aria-hidden={!isLoading}
                  focusable="false"
                  role="progressbar"
                  viewBox="0 0 20 20"
                >
                  <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                </LoaderAnimation>
              </Presence>
            </LoaderContainer>
          )}
          {!isLoading && (
            <SearchResultsLayout.Wrapper ref={widgetRef}>
              <SearchResultsLayout.MainArea>
                {isFetching && (
                  <LoaderContainer>
                    <Presence present={true}>
                      <LoaderAnimation
                        aria-busy={true}
                        aria-hidden={false}
                        focusable="false"
                        role="progressbar"
                        viewBox="0 0 20 20"
                      >
                        <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                      </LoaderAnimation>
                    </Presence>
                  </LoaderContainer>
                )}
                {totalItems > 0 && (
                  <>
                    <SearchResultsLayout.LeftArea>
                      {selectedFacetsFromApi.length > 0 && (
                        <FiltersStyled.ClearFilters onClick={onClearFilters}>Clear Filters</FiltersStyled.ClearFilters>
                      )}
                      <FiltersStyled.SelectedFiltersList>
                        {selectedFacetsFromApi.map((selectedFacet) => (
                          <FiltersStyled.SelectedFiltersListItem
                            key={`${selectedFacet.facetId}${selectedFacet.facetLabel}${selectedFacet.valueLabel}`}
                          >
                            <FiltersStyled.SelectedFiltersListItemText>
                              {buildFacetLabel(selectedFacet)}
                            </FiltersStyled.SelectedFiltersListItemText>
                            <FiltersStyled.SelectedFiltersListItemButton onClick={() => onRemoveFilter(selectedFacet)}>
                              X
                            </FiltersStyled.SelectedFiltersListItemButton>
                          </FiltersStyled.SelectedFiltersListItem>
                        ))}
                      </FiltersStyled.SelectedFiltersList>
                      <AccordionFacetsStyled.Root
                        defaultFacetTypesExpandedList={[]}
                        onFacetTypesExpandedListChange={() => {}}
                        onFacetValueClick={onFacetClick}
                      >
                        {facets.map((f) => (
                          <AccordionFacetsStyled.Facet facetId={f.name} key={f.name}>
                            <AccordionFacetsStyled.Header>
                              <AccordionFacetsStyled.Trigger>{f.label}</AccordionFacetsStyled.Trigger>
                            </AccordionFacetsStyled.Header>
                            <AccordionFacets.Content>
                              <AccordionFacetsStyled.ValueList>
                                {f.value.map((v, index) => (
                                  <AccordionFacetsStyled.Item {...{ index, facetValueId: v.id }} key={v.id}>
                                    <AccordionFacetsStyled.ItemCheckbox>
                                      <AccordionFacetsStyled.ItemCheckboxIndicator>
                                        <CheckIcon />
                                      </AccordionFacetsStyled.ItemCheckboxIndicator>
                                    </AccordionFacetsStyled.ItemCheckbox>
                                    <AccordionFacetsStyled.ItemCheckboxLabel>
                                      {v.text} {v.count && `(${v.count})`}
                                    </AccordionFacetsStyled.ItemCheckboxLabel>
                                  </AccordionFacetsStyled.Item>
                                ))}
                              </AccordionFacetsStyled.ValueList>
                            </AccordionFacets.Content>
                          </AccordionFacetsStyled.Facet>
                        ))}
                      </AccordionFacetsStyled.Root>
                    </SearchResultsLayout.LeftArea>
                    <SearchResultsLayout.RightArea>
                      <SearchResultsLayout.RightTopArea>
                        {totalItems && (
                          <QuerySummaryStyled>
                            <b>
                              Showing {itemsPerPage * (page - 1) + 1} - {itemsPerPage * (page - 1) + articles.length} of{' '}
                              {totalItems} results
                            </b>
                          </QuerySummaryStyled>
                        )}
    
                        <div>
                          {/* Card View Switcher */}
                          <CardViewSwitcherStyled.Root onValueChange={onToggle} defaultValue={defaultCardView}>
                            <CardViewSwitcherStyled.Item value="grid" aria-label="Grid View">
                              <GridIcon />
                            </CardViewSwitcherStyled.Item>
                            <CardViewSwitcherStyled.Item value="list" aria-label="List View">
                              <ListBulletIcon />
                            </CardViewSwitcherStyled.Item>
                          </CardViewSwitcherStyled.Root>
    
                          {/* Sort Select */}
                          <SortSelect.Root defaultValue={sortChoices[selectedSortIndex]?.name} onValueChange={onSortChange}>
                            <SortSelectStyled.Trigger>
                              <SortSelectStyled.SelectValue>
                                {selectedSortIndex > -1 ? sortChoices[selectedSortIndex].label : ''}
                              </SortSelectStyled.SelectValue>
                              <SortSelectStyled.Icon />
                            </SortSelectStyled.Trigger>
                            <SortSelectStyled.Content>
                              <SortSelectStyled.Viewport>
                                {sortChoices.map((option) => (
                                  <SortSelectStyled.Option value={option} key={option.name}>
                                    <SortSelectStyled.OptionText>{option.label}</SortSelectStyled.OptionText>
                                  </SortSelectStyled.Option>
                                ))}
                              </SortSelectStyled.Viewport>
                            </SortSelectStyled.Content>
                          </SortSelect.Root>
                        </div>
                      </SearchResultsLayout.RightTopArea>
    
                      {/* Results */}
                      {dir === CardViewSwitcher.CARD_VIEW_GRID ? (
                        <GridStyled>
                          {articles.map((a, index) => (
                            <ArticleCardStyled.Root key={a.id}>
                              <ArticleCardStyled.ImageWrapper>
                                <ArticleCardStyled.Image src={a.image_url} />
                              </ArticleCardStyled.ImageWrapper>
                              <ArticleCardStyled.Id>{a.id}</ArticleCardStyled.Id>
                              <ArticleCardStyled.Title>
                                <ArticleCardStyled.Link
                                  href="#"
                                  onClick={(e) => {
                                    e.preventDefault();
                                    onItemClick({ id: a.id, index, sourceId: a.source_id });
                                  }}
                                >
                                  {a.name}
                                </ArticleCardStyled.Link>
                              </ArticleCardStyled.Title>
                            </ArticleCardStyled.Root>
                          ))}
                        </GridStyled>
                      ) : (
                        <RowStyled>
                          {articles.map((a, index) => (
                            <ArticleCardRowStyled.Root key={a.id}>
                              <ArticleCardRowStyled.Left>
                                <ArticleCardRowStyled.Image src={a.image_url} />
                              </ArticleCardRowStyled.Left>
                              <ArticleCardRowStyled.Right>
                                <ArticleCardRowStyled.Title>
                                  <ArticleCardRowStyled.Link
                                    href="#"
                                    onClick={(e) => {
                                      e.preventDefault();
                                      onItemClick({ id: a.id, index, sourceId: a.source_id });
                                    }}
                                  >
                                    {a.name}
                                  </ArticleCardRowStyled.Link>
                                </ArticleCardRowStyled.Title>
                                <ArticleCardRowStyled.Content>{a.content_text}</ArticleCardRowStyled.Content>
                              </ArticleCardRowStyled.Right>
                            </ArticleCardRowStyled.Root>
                          ))}
                        </RowStyled>
                      )}
                      <PageControlsStyled>
                        <div>
                          <label>Results Per Page</label>
                          <Select.Root
                            defaultValue={String(defaultItemsPerPage)}
                            onValueChange={(v) => onResultsPerPageChange({ numItems: Number(v) })}
                          >
                            <SelectStyled.Trigger>
                              <SelectStyled.SelectValue />
                              <SelectStyled.Icon />
                            </SelectStyled.Trigger>
                            <SelectStyled.Content>
                              <SelectStyled.Viewport>
                                <SelectStyled.Option value="24">
                                  <SelectStyled.OptionText>24</SelectStyled.OptionText>
                                </SelectStyled.Option>
    
                                <SelectStyled.Option value="48">
                                  <SelectStyled.OptionText>48</SelectStyled.OptionText>
                                </SelectStyled.Option>
    
                                <SelectStyled.Option value="64">
                                  <SelectStyled.OptionText>64</SelectStyled.OptionText>
                                </SelectStyled.Option>
                              </SelectStyled.Viewport>
                            </SelectStyled.Content>
                          </Select.Root>
                        </div>
                        <PaginationStyled.Root
                          currentPage={page}
                          defaultCurrentPage={1}
                          totalPages={totalPages}
                          onPageChange={(v) => onPageNumberChange({ page: v })}
                        >
                          <PaginationStyled.PrevPage onClick={(e) => e.preventDefault()}>
                            <ArrowLeftIcon />
                          </PaginationStyled.PrevPage>
                          <PaginationStyled.Pages>
                            {(pagination) =>
                              Pagination.paginationLayout(pagination, {}).map(({ page, type }) =>
                                type === 'page' ? (
                                  <PaginationStyled.Page
                                    key={page}
                                    aria-label={`Page ${page}`}
                                    page={page as number}
                                    onClick={(e) => e.preventDefault()}
                                  >
                                    {page}
                                  </PaginationStyled.Page>
                                ) : (
                                  <span key={type}>...</span>
                                ),
                              )
                            }
                          </PaginationStyled.Pages>
                          <PaginationStyled.NextPage onClick={(e) => e.preventDefault()}>
                            <ArrowRightIcon />
                          </PaginationStyled.NextPage>
                        </PaginationStyled.Root>
                      </PageControlsStyled>
                    </SearchResultsLayout.RightArea>
                  </>
                )}
                {totalItems <= 0 && !isFetching && (
                  <SearchResultsLayout.NoResults>
                    <h3>0 Results</h3>
                  </SearchResultsLayout.NoResults>
                )}
              </SearchResultsLayout.MainArea>
            </SearchResultsLayout.Wrapper>
          )}
        </>
      );
    };
    
    const SearchResultsWithLayoutOptionWidget = widget(SearchResultsWithLayoutOptionComponent, WidgetDataType.SEARCH_RESULTS, 'content');
    
    export default SearchResultsWithLayoutOptionWidget;
    // styled.ts
    import { keyframes, styled } from 'styled-components';
    
    import {
      AccordionFacets,
      ArticleCard,
      Breadcrumb,
      CardViewSwitcher,
      FacetItem,
      Pagination,
      SearchResultsAccordionFacets,
      Select,
      SortSelect,
      theme,
    } from '@sitecore-search/ui';
    
    const ArticleCardRowRootStyled = styled(ArticleCard.Root)`
      box-sizing: border-box;
      border: solid 1px ${theme.vars.palette.grey['400']};
      box-shadow: 2px 2px 4px ${theme.vars.palette.grey['400']};
      display: flex;
      height: 200px;
      width: 100%;
      padding: ${theme.vars.spacing.m};
      margin-bottom: ${theme.vars.spacing.m};
      cursor: pointer;
      &:focus-within {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
      &:hover {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
    `;
    
    const ArticleCardRowLeftStyled = styled.div`
      position: relative;
      height: 164px;
      width: 164px;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    `;
    const ArticleCardRowRightStyled = styled.div`
      box-sizing: border-box;
      padding-left: ${theme.vars.spacing.m};
      overflow: hidden;
      max-width: 80%;
    `;
    
    const ArticleCardRowImageStyled = styled(ArticleCard.Image)`
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    `;
    
    const ArticleCardRowTitleStyled = styled(ArticleCard.Title)`
      margin: 0 0 ${theme.vars.spacing.m};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize4.fontSize};
      font-weight: ${theme.vars.typography.fontSize4.fontWeight};
      line-height: ${theme.vars.typography.fontSize4.lineHeight};
    `;
    
    const ArticleCardRowContentStyled = styled(ArticleCard.Content)`
      margin: 0;
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      font-weight: ${theme.vars.typography.fontWeight};
      line-height: ${theme.vars.typography.lineHeight};
      color: ${theme.vars.palette.primary.main};
    `;
    
    const ArticleCardRowIdStyled = styled(ArticleCard.Id)`
      font-family: ${theme.vars.typography.fontFamilySystem};
      color: ${theme.vars.palette.primary.main};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
    `;
    
    const ArticleCardRowLinkStyled = styled.a`
      text-decoration: none;
      color: ${theme.vars.palette.primary.main};
      font-size: ${theme.vars.typography.fontSize4.fontSize};
      &:hover {
        text-decoration: none;
      }
      &:focus {
        text-decoration: none;
      }
    `;
    
    export const ArticleCardRowStyled = {
      Link: ArticleCardRowLinkStyled,
      Id: ArticleCardRowIdStyled,
      Content: ArticleCardRowContentStyled,
      Image: ArticleCardRowImageStyled,
      Title: ArticleCardRowTitleStyled,
      Root: ArticleCardRowRootStyled,
      Left: ArticleCardRowLeftStyled,
      Right: ArticleCardRowRightStyled,
    };
    
    const CardViewSwitcherRoot = styled(CardViewSwitcher.Root)`
      display: inline-flex;
      background-color: ${theme.vars.palette.common.white};
    `;
    
    const CardViewSwitcherItem = styled(CardViewSwitcher.Item)`
      all: unset;
      align-items: center;
      background-color: ${theme.vars.palette.common.white};
      color: ${theme.vars.palette.grey[500]};
      display: flex;
      height: 30px;
      justify-content: center;
      margin-left: ${theme.vars.spacing.s};
      width: 30px;
    
      &:first-child {
        margin-left: 0;
      }
    
      &:hover {
        background-color: ${theme.vars.palette.primary.light};
      }
      &[data-state='on'] {
        background-color: ${theme.vars.palette.primary.main};
        color: ${theme.vars.palette.primary.contrastText};
      }
    
      &:focus {
        position: relative;
        border: solid 1px ${theme.vars.palette.primary.main};
      }
    `;
    
    export const CardViewSwitcherStyled = {
      Root: CardViewSwitcherRoot,
      Item: CardViewSwitcherItem,
    };
    
    export const RowStyled = styled.div`
      width: 100%;
      display: block;
    `;
    
    const BreadcrumbRootStyled = styled(Breadcrumb.Root)`
      width: 100%;
      display: inline-block;
    `;
    
    const BreadcrumbListStyled = styled(Breadcrumb.List)`
      list-style: none;
      padding: 0;
    `;
    
    const BreadcrumbItemStyled = styled(Breadcrumb.Item)`
      display: inline;
      color: ${theme.vars.palette.primary.main};
      margin-left: ${theme.vars.spacing.xs};
    `;
    
    const BreadcrumbNavigationStyled = styled(Breadcrumb.Navigation)``;
    
    const BreadcrumbLinkStyled = styled(Breadcrumb.Link)`
      color: ${theme.vars.palette.primary.main};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      text-decoration: none;
    `;
    
    const BreadcrumbSeparatorStyled = styled(Breadcrumb.Separator)``;
    
    export const BreadcrumbStyled = {
      Root: BreadcrumbRootStyled,
      List: BreadcrumbListStyled,
      Item: BreadcrumbItemStyled,
      Navigation: BreadcrumbNavigationStyled,
      Link: BreadcrumbLinkStyled,
      Separator: BreadcrumbSeparatorStyled,
    };
    
    const selectTriggerStyle = `
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: ${theme.vars.spacing.xs};
      background-color: transparent;
      height: 40px;
      padding: ${theme.vars.spacing.xs} ${theme.vars.spacing.m};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      color: ${theme.vars.palette.primary.main};
      border: none;
      &:focus {
        outline: none;
      }
    `;
    
    const SortSelectTriggerStyled = styled(SortSelect.Trigger)`
      ${selectTriggerStyle}
    `;
    
    const GenericSelectTriggerStyled = styled(Select.Trigger)`
      ${selectTriggerStyle}
    `;
    
    const contentSelectStyle = `
      background-color: ${theme.vars.palette.primary.contrastText};
      position: relative;
      overflow: hidden;
      color: ${theme.vars.palette.primary.main};
      box-shadow: 2px 2px 4px ${theme.vars.palette.grey['400']};
      position: absolute;
      top: 30px;
      z-index: 100;
      &:focus-within {
        border-color: ${theme.vars.palette.primary.dark};
      }
    `;
    
    const SortSelectContentStyled = styled(SortSelect.Content)`
      ${contentSelectStyle}
    `;
    
    const GenericSelectContentStyled = styled(Select.SelectContent)`
      ${contentSelectStyle}
    `;
    
    const viewportSelectStyles = `
      padding: ${theme.vars.spacing.xs};
      z-index: 50000;
    `;
    
    const SortSelectViewportStyled = styled(SortSelect.Viewport)`
      ${viewportSelectStyles}
    `;
    
    const GenericSelectViewportStyled = styled(Select.Viewport)`
      ${viewportSelectStyles}
    `;
    
    const optionSelectStyles = `
      display: flex;
      align-items: center;
      line-height: 1;
      cursor: pointer;
      user-select: none;
      white-space: nowrap;
      height: 25px;
      padding: 0 ${theme.vars.spacing.xs};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      color: ${theme.vars.palette.primary.main};
      position: relative;
      border-radius: 0px;
    
      &[data-highlighted] {
        border-radius: 0;
        background-color: ${theme.vars.palette.primary.dark};
        color: ${theme.vars.palette.primary.contrastText};
      }
      &[data-disabled] {
        color: ${theme.vars.palette.grey['800']};
        font-style: italic;
      }
    `;
    
    const SortSelectOptionStyled = styled(SortSelect.Option)`
      ${optionSelectStyles}
    `;
    
    const GenericSelectOptionStyled = styled(Select.SelectItem)`
      ${optionSelectStyles}
    `;
    
    const SortSelectValueStyled = styled(SortSelect.SelectValue)`
      color: ${theme.vars.palette.primary.main};
    `;
    
    const GenericSelectValueStyled = styled(Select.SelectValue)`
      color: ${theme.vars.palette.primary.main};
    `;
    
    const SortSelectIconStyled = styled(SortSelect.Icon)``;
    
    const GenericSelectIconStyled = styled(Select.Icon)``;
    
    const SortSelectOptionTextStyled = styled(SortSelect.OptionText)``;
    const GenericSelectOptionTextStyled = styled(SortSelect.OptionText)``;
    
    export const SortSelectStyled = {
      Trigger: SortSelectTriggerStyled,
      Content: SortSelectContentStyled,
      Viewport: SortSelectViewportStyled,
      Option: SortSelectOptionStyled,
      SelectValue: SortSelectValueStyled,
      OptionText: SortSelectOptionTextStyled,
      Icon: SortSelectIconStyled,
    };
    
    export const SelectStyled = {
      Trigger: GenericSelectTriggerStyled,
      Icon: GenericSelectIconStyled,
      SelectValue: GenericSelectValueStyled,
      Content: GenericSelectContentStyled,
      Viewport: GenericSelectViewportStyled,
      Option: GenericSelectOptionStyled,
      OptionText: GenericSelectOptionTextStyled,
    };
    
    const ArticleRootStyled = styled(ArticleCard.Root)`
      border: solid 1px ${theme.vars.palette.grey['400']};
      box-shadow: 2px 2px 4px ${theme.vars.palette.grey['400']};
      width: 180px;
      padding: ${theme.vars.spacing.m};
      cursor: pointer;
      display: block;
      &:focus-within {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
      &:hover {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
    `;
    
    const ArticleImageStyled = styled(ArticleCard.Image)`
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    `;
    
    const ImageWrapperStyled = styled.div`
      margin: auto auto 10px;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    `;
    
    const ArticleTitleStyled = styled(ArticleCard.Title)`
      margin: 0 0 ${theme.vars.spacing.m};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize3.fontSize};
      font-weight: ${theme.vars.typography.fontSize3.fontWeight};
      line-height: ${theme.vars.typography.fontSize3.lineHeight};
    `;
    
    const ArticleContentStyled = styled(ArticleCard.Content)`
      margin: 0;
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      font-weight: ${theme.vars.typography.fontWeight};
      line-height: ${theme.vars.typography.lineHeight};
      color: ${theme.vars.palette.primary.main};
    `;
    
    const ArticleIdStyled = styled(ArticleCard.Id)`
      font-family: ${theme.vars.typography.fontFamilySystem};
      color: ${theme.vars.palette.primary.main};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
    `;
    
    const ArticleLinkStyled = styled.a`
      text-decoration: none;
      color: ${theme.vars.palette.primary.main};
      font-size: ${theme.vars.typography.fontSize4.fontSize};
      &:hover {
        text-decoration: none;
      }
      &:focus {
        text-decoration: none;
      }
    `;
    
    export const ArticleCardStyled = {
      Link: ArticleLinkStyled,
      Id: ArticleIdStyled,
      Content: ArticleContentStyled,
      ImageWrapper: ImageWrapperStyled,
      Image: ArticleImageStyled,
      Title: ArticleTitleStyled,
      Root: ArticleRootStyled,
    };
    
    const AccordionItemCheckboxStyled = styled(AccordionFacets.ItemCheckbox)`
      all: unset;
      background-color: white;
      width: 20px;
      height: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid #ccc;
      border-radius: 4px;
      cursor: pointer;
      &[data-state='checked'] {
        color: ${theme.vars.palette.primary.contrastText};
        background-color: ${theme.vars.palette.primary.main};
      }
    
      &:focus {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
    `;
    
    const AccordionItemToggleStyled = styled(AccordionFacets.ItemToggle)`
      all: unset;
      width: 40px;
      height: 35px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      margin-right: ${theme.vars.spacing.s};
    
      &:focus {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
    
      &[data-state='on'] {
        background-color: ${theme.vars.palette.primary.main};
        color: ${theme.vars.palette.primary.contrastText};
      }
    `;
    
    const AccordionItemCheckboxIndicatorStyled = styled(AccordionFacets.ItemCheckboxIndicator)`
      color: ${theme.vars.palette.primary.contrastText};
      width: 15px;
      height: 15px;
    `;
    
    const AccordionValueListStyled = styled(AccordionFacets.ValueList)`
      list-style: none;
      li {
        padding: ${theme.vars.spacing.xs} 0;
        font-family: ${theme.vars.typography.fontFamilySystem};
        font-size: ${theme.vars.typography.fontSize1.fontSize};
      }
    
      &[data-orientation='horizontal'] {
        display: flex;
        flex-direction: row;
      }
    `;
    
    const AccordionItemCheckboxLabelStyled = styled(AccordionFacets.ItemLabel)`
      padding-left: ${theme.vars.spacing.xs};
      color: ${theme.vars.palette.primary.main};
    `;
    
    const AccordionItemStyled = styled(FacetItem)`
      display: flex;
      align-items: center;
    `;
    
    const AccordionHeaderStyled = styled(AccordionFacets.Header)`
      display: flex;
      margin-top: ${theme.vars.spacing.s};
      margin-bottom: ${theme.vars.spacing.s};
    `;
    const AccordionTriggerStyled = styled(AccordionFacets.Trigger)`
      align-items: center;
      color: ${theme.vars.palette.primary.main};
      display: flex;
      font-size: ${theme.vars.typography.fontSize2.fontSize};
      height: 45px;
      justify-content: space-between;
      line-height: 1;
      padding: 0 ${theme.vars.spacing.m};
      flex: 1 1 0;
      background: none;
      border: none;
    `;
    
    const AccordionFacetsFacetStyled = styled(AccordionFacets.Facet)`
      border-bottom: solid 1px ${theme.vars.palette.grey['800']};
    `;
    
    const AccordionFacetsRootStyled = styled(SearchResultsAccordionFacets)``;
    
    export const AccordionFacetsStyled = {
      Trigger: AccordionTriggerStyled,
      Header: AccordionHeaderStyled,
      Item: AccordionItemStyled,
      ItemCheckboxLabel: AccordionItemCheckboxLabelStyled,
      ValueList: AccordionValueListStyled,
      ItemCheckboxIndicator: AccordionItemCheckboxIndicatorStyled,
      ItemToggle: AccordionItemToggleStyled,
      ItemCheckbox: AccordionItemCheckboxStyled,
      Facet: AccordionFacetsFacetStyled,
      Root: AccordionFacetsRootStyled,
    };
    
    const paginationLinkStyle = `
      cursor: pointer;
      margin: 0 5px;
    
      &[data-current='true'] {
        color: gray;
        pointer-events: none;
        text-decoration-line: none;
      }
    `;
    
    const paginationNavigationLinkStyle = `
      ${paginationLinkStyle}
      &[data-current='true'] {
        display: none;
      }
    `;
    
    const PaginationRootStyled = styled(Pagination.Root)`
      color: ${theme.vars.palette.primary.main};
      font-family: ${theme.vars.typography.fontFamilySystem};
      margin-top: ${theme.vars.spacing.m};
    `;
    const PaginationPrevPageStyled = styled(Pagination.PrevPage)`
      display: inline;
      ${paginationNavigationLinkStyle}
    `;
    const PaginationNextPageStyled = styled(Pagination.NextPage)`
      display: inline;
      ${paginationNavigationLinkStyle}
    `;
    const PaginationFirstPageStyled = styled(Pagination.FirstPage)`
      font-size: ${theme.vars.typography.fontSize2.fontSize};
      ${paginationNavigationLinkStyle}
    `;
    const PaginationLastPageStyled = styled(Pagination.LastPage)`
      font-size: ${theme.vars.typography.fontSize2.fontSize};
      ${paginationLinkStyle}
    `;
    const PaginationPageStyled = styled(Pagination.Page)`
      cursor: pointer;
      ${paginationLinkStyle}
    `;
    const PaginationPagesStyled = styled(Pagination.Pages)`
      display: inline;
    `;
    
    export const PaginationStyled = {
      Root: PaginationRootStyled,
      PrevPage: PaginationPrevPageStyled,
      NextPage: PaginationNextPageStyled,
      FirstPage: PaginationFirstPageStyled,
      LastPage: PaginationLastPageStyled,
      Page: PaginationPageStyled,
      Pages: PaginationPagesStyled,
    };
    
    const Wrapper = styled.div``;
    
    const MainArea = styled.div`
      display: flex;
      max-width: 100%;
      position: relative;
    `;
    
    const LeftArea = styled.section`
      position: relative;
      flex: 1 1;
      margin-right: ${theme.vars.spacing.l};
    `;
    
    const RightArea = styled.section`
      display: flex;
      flex-direction: column;
      flex: 4 1;
    `;
    
    const RightTopArea = styled.section`
      display: flex;
      justify-content: space-between;
    `;
    
    export const GridStyled = styled.div`
      width: 100%;
      display: grid;
      grid-gap: ${theme.vars.spacing.m};
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      grid-auto-flow: row;
    `;
    
    const ClearFilters = styled.button`
      background: none;
      border: none;
      color: ${theme.vars.palette.primary.main};
      &:hover {
        text-decoration: underline;
        cursor: pointer;
      }
    `;
    
    const SelectedFiltersList = styled.ul`
      width: 100%;
      list-style: none;
      padding-left: ${theme.vars.spacing.s};
    `;
    
    const SelectedFiltersListItem = styled.li`
      display: flex;
      justify-content: space-between;
      padding: ${theme.vars.spacing.xs} 0 ${theme.vars.spacing.xs} 0;
    `;
    
    const SelectedFiltersListItemText = styled.span`
      color: ${theme.vars.palette.primary.main};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
    `;
    
    const SelectedFiltersListItemButton = styled.button`
      background: none;
      border: none;
      color: ${theme.vars.palette.primary.main};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      cursor: pointer;
    `;
    
    export const PageControlsStyled = styled.div`
      display: flex;
      justify-content: space-between;
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
    `;
    
    export const QuerySummaryStyled = styled.div`
      color: ${theme.vars.palette.primary.main};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      font-weight: bold;
      margin: auto 0;
    `;
    
    export const LoaderContainer = styled.div`
      align-items: center;
      display: flex;
      position: absolute;
      left: 0;
      right: 0;
      height: 100%;
      background-color: ${theme.vars.palette.common.white};
      opacity: 0.5;
      z-index: 100;
    `;
    
    const Rotate = keyframes`
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    `;
    
    export const LoaderAnimation = styled.svg`
      animation: ${Rotate} 2s linear infinite;
      display: block;
      fill: ${theme.vars.palette.primary.main};
      height: 50px;
      margin: auto;
      width: 50px;
    `;
    
    export const FiltersStyled = {
      ClearFilters,
      SelectedFiltersList,
      SelectedFiltersListItem,
      SelectedFiltersListItemText,
      SelectedFiltersListItemButton,
    };
    
    export const NoResults = styled.div`
      display: flex;
      justify-content: center;
      width: 100%;
    `;
    
    export const SearchResultsLayout = {
      Wrapper,
      MainArea,
      NoResults,
      LeftArea,
      RightArea,
      RightTopArea,
    };

JS CLI

With the Sitecore Search CLI you can replicate all necessary files of the widget template in your React project. The following procedure includes instructions for installing the CLI in your project and specifying the location where widget components are created.

If you have previously installed the CLI in your project and have a location defined in a file called .sc-search-settings.json, you can proceed directly to step 3.

To install the UI Components CLI in your project:

  1. To install the Search UI Components CLI, open the terminal of your IDE or command prompt and run the following command:

    npx install --save-dev @sitecore-search/cli
  2. To define the location of where widget components will be created, at the root of your project, in a file called .sc-search-settings.json, specify the location as shown in the following code block.

    {
        "components-path": "src-test/components"
    }
  3. To create the widget component in index.jsx and styled.js files, go to the location you defined in the .sc-search-settings.json file then open the terminal of your IDE or command prompt and run the following command:

    npx sc-search new-widget --language javascript --template search-results-row-grid --entity content --styling styled

JS manual

When you manually create widget templates in your React project, you need to ensure that you install any required dependencies and create all necessary files.

if you have previously installed the required NPM packages, you can skip the dependency installation steps.

To create the widget component using this widget template in your project:

  1. In your project, to install development dependencies, open the terminal of your IDE or command prompt and run the following command:

    npm install styled-components@^6.1.0 @radix-ui/react-icons@^1.3.0
  2. In your project, to install the development dependency, open the terminal of your IDE or command prompt and run the following command:

    npm install --save-dev @types/styled-components@^5.1.29
  3. In a folder for the widget component, create files called index.jsx and styled.js then in respective files, paste following code blocks.

    // index.jsx
    import { useState } from 'react';
    
    import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, GridIcon, ListBulletIcon } from '@radix-ui/react-icons';
    import { Presence } from '@radix-ui/react-presence';
    import { WidgetDataType, useSearchResults, useSearchResultsSelectedFilters, widget } from '@sitecore-search/react';
    import { AccordionFacets, CardViewSwitcher, Pagination, Select, SortSelect } from '@sitecore-search/ui';
    
    import {
      AccordionFacetsStyled,
      ArticleCardRowStyled,
      ArticleCardStyled,
      CardViewSwitcherStyled,
      FiltersStyled,
      GridStyled,
      LoaderAnimation,
      LoaderContainer,
      PageControlsStyled,
      PaginationStyled,
      QuerySummaryStyled,
      RowStyled,
      SearchResultsLayout,
      SelectStyled,
      SortSelectStyled,
    } from './styled';
    
    const buildRangeLabel = (min, max) => {
      return typeof min === 'undefined' ? `< $${max}` : typeof max === 'undefined' ? ` > $${min}` : `$${min} - $${max}`;
    };
    const buildFacetLabel = (selectedFacet) => {
      if ('min' in selectedFacet || 'max' in selectedFacet) {
        return `${selectedFacet.facetLabel}: ${buildRangeLabel(selectedFacet.min, selectedFacet.max)}`;
      }
      return `${selectedFacet.facetLabel}: ${selectedFacet.valueLabel}`;
    };
    export const SearchResultsWithLayoutOptionComponent = ({
      defaultSortType = 'featured_desc',
      defaultPage = 1,
      defaultKeyphrase = '',
      defaultItemsPerPage = 24,
    }) => {
      const {
        widgetRef,
        actions: {
          onResultsPerPageChange,
          onPageNumberChange,
          onItemClick,
          onRemoveFilter,
          onSortChange,
          onFacetClick,
          onClearFilters,
        },
        state: { sortType, page, itemsPerPage },
        queryResult: {
          isLoading,
          isFetching,
          data: {
            total_item: totalItems = 0,
            sort: { choices: sortChoices = [] } = {},
            facet: facets = [],
            content: articles = [],
          } = {},
        },
      } = useSearchResults({
        state: {
          sortType: defaultSortType,
          page: defaultPage,
          itemsPerPage: defaultItemsPerPage,
          keyphrase: defaultKeyphrase,
        },
      });
      const totalPages = Math.ceil(totalItems / itemsPerPage);
      const selectedSortIndex = sortChoices.findIndex((s) => s.name === sortType);
      const selectedFacetsFromApi = useSearchResultsSelectedFilters();
      const defaultCardView = CardViewSwitcher.CARD_VIEW_LIST;
      const [dir, setDir] = useState(defaultCardView);
      const onToggle = (value = defaultCardView) => setDir(value);
      return (
        <>
          {isLoading && (
            <LoaderContainer>
              <Presence present={isLoading}>
                <LoaderAnimation
                  aria-busy={isLoading}
                  aria-hidden={!isLoading}
                  focusable="false"
                  role="progressbar"
                  viewBox="0 0 20 20"
                >
                  <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                </LoaderAnimation>
              </Presence>
            </LoaderContainer>
          )}
          {!isLoading && (
            <SearchResultsLayout.Wrapper ref={widgetRef}>
              <SearchResultsLayout.MainArea>
                {isFetching && (
                  <LoaderContainer>
                    <Presence present={true}>
                      <LoaderAnimation
                        aria-busy={true}
                        aria-hidden={false}
                        focusable="false"
                        role="progressbar"
                        viewBox="0 0 20 20"
                      >
                        <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                      </LoaderAnimation>
                    </Presence>
                  </LoaderContainer>
                )}
                {totalItems > 0 && (
                  <>
                    <SearchResultsLayout.LeftArea>
                      {selectedFacetsFromApi.length > 0 && (
                        <FiltersStyled.ClearFilters onClick={onClearFilters}>Clear Filters</FiltersStyled.ClearFilters>
                      )}
                      <FiltersStyled.SelectedFiltersList>
                        {selectedFacetsFromApi.map((selectedFacet) => (
                          <FiltersStyled.SelectedFiltersListItem
                            key={`${selectedFacet.facetId}${selectedFacet.facetLabel}${selectedFacet.valueLabel}`}
                          >
                            <FiltersStyled.SelectedFiltersListItemText>
                              {buildFacetLabel(selectedFacet)}
                            </FiltersStyled.SelectedFiltersListItemText>
                            <FiltersStyled.SelectedFiltersListItemButton onClick={() => onRemoveFilter(selectedFacet)}>
                              X
                            </FiltersStyled.SelectedFiltersListItemButton>
                          </FiltersStyled.SelectedFiltersListItem>
                        ))}
                      </FiltersStyled.SelectedFiltersList>
                      <AccordionFacetsStyled.Root
                        defaultFacetTypesExpandedList={[]}
                        onFacetTypesExpandedListChange={() => {}}
                        onFacetValueClick={onFacetClick}
                      >
                        {facets.map((f) => (
                          <AccordionFacetsStyled.Facet facetId={f.name} key={f.name}>
                            <AccordionFacetsStyled.Header>
                              <AccordionFacetsStyled.Trigger>{f.label}</AccordionFacetsStyled.Trigger>
                            </AccordionFacetsStyled.Header>
                            <AccordionFacets.Content>
                              <AccordionFacetsStyled.ValueList>
                                {f.value.map((v, index) => (
                                  <AccordionFacetsStyled.Item
                                    {...{
                                      index,
                                      facetValueId: v.id,
                                    }}
                                    key={v.id}
                                  >
                                    <AccordionFacetsStyled.ItemCheckbox>
                                      <AccordionFacetsStyled.ItemCheckboxIndicator>
                                        <CheckIcon />
                                      </AccordionFacetsStyled.ItemCheckboxIndicator>
                                    </AccordionFacetsStyled.ItemCheckbox>
                                    <AccordionFacetsStyled.ItemCheckboxLabel>
                                      {v.text} {v.count && `(${v.count})`}
                                    </AccordionFacetsStyled.ItemCheckboxLabel>
                                  </AccordionFacetsStyled.Item>
                                ))}
                              </AccordionFacetsStyled.ValueList>
                            </AccordionFacets.Content>
                          </AccordionFacetsStyled.Facet>
                        ))}
                      </AccordionFacetsStyled.Root>
                    </SearchResultsLayout.LeftArea>
                    <SearchResultsLayout.RightArea>
                      <SearchResultsLayout.RightTopArea>
                        {totalItems && (
                          <QuerySummaryStyled>
                            <b>
                              Showing {itemsPerPage * (page - 1) + 1} - {itemsPerPage * (page - 1) + articles.length} of{' '}
                              {totalItems} results
                            </b>
                          </QuerySummaryStyled>
                        )}
    
                        <div>
                          {/* Card View Switcher */}
                          <CardViewSwitcherStyled.Root onValueChange={onToggle} defaultValue={defaultCardView}>
                            <CardViewSwitcherStyled.Item value="grid" aria-label="Grid View">
                              <GridIcon />
                            </CardViewSwitcherStyled.Item>
                            <CardViewSwitcherStyled.Item value="list" aria-label="List View">
                              <ListBulletIcon />
                            </CardViewSwitcherStyled.Item>
                          </CardViewSwitcherStyled.Root>
    
                          {/* Sort Select */}
                          <SortSelect.Root defaultValue={sortChoices[selectedSortIndex]?.name} onValueChange={onSortChange}>
                            <SortSelectStyled.Trigger>
                              <SortSelectStyled.SelectValue>
                                {selectedSortIndex > -1 ? sortChoices[selectedSortIndex].label : ''}
                              </SortSelectStyled.SelectValue>
                              <SortSelectStyled.Icon />
                            </SortSelectStyled.Trigger>
                            <SortSelectStyled.Content>
                              <SortSelectStyled.Viewport>
                                {sortChoices.map((option) => (
                                  <SortSelectStyled.Option value={option} key={option.name}>
                                    <SortSelectStyled.OptionText>{option.label}</SortSelectStyled.OptionText>
                                  </SortSelectStyled.Option>
                                ))}
                              </SortSelectStyled.Viewport>
                            </SortSelectStyled.Content>
                          </SortSelect.Root>
                        </div>
                      </SearchResultsLayout.RightTopArea>
    
                      {/* Results */}
                      {dir === CardViewSwitcher.CARD_VIEW_GRID ? (
                        <GridStyled>
                          {articles.map((a, index) => (
                            <ArticleCardStyled.Root key={a.id}>
                              <ArticleCardStyled.ImageWrapper>
                                <ArticleCardStyled.Image src={a.image_url} />
                              </ArticleCardStyled.ImageWrapper>
                              <ArticleCardStyled.Id>{a.id}</ArticleCardStyled.Id>
                              <ArticleCardStyled.Title>
                                <ArticleCardStyled.Link
                                  href="#"
                                  onClick={(e) => {
                                    e.preventDefault();
                                    onItemClick({
                                      id: a.id,
                                      index,
                                      sourceId: a.source_id,
                                    });
                                  }}
                                >
                                  {a.name}
                                </ArticleCardStyled.Link>
                              </ArticleCardStyled.Title>
                            </ArticleCardStyled.Root>
                          ))}
                        </GridStyled>
                      ) : (
                        <RowStyled>
                          {articles.map((a, index) => (
                            <ArticleCardRowStyled.Root key={a.id}>
                              <ArticleCardRowStyled.Left>
                                <ArticleCardRowStyled.Image src={a.image_url} />
                              </ArticleCardRowStyled.Left>
                              <ArticleCardRowStyled.Right>
                                <ArticleCardRowStyled.Title>
                                  <ArticleCardRowStyled.Link
                                    href="#"
                                    onClick={(e) => {
                                      e.preventDefault();
                                      onItemClick({
                                        id: a.id,
                                        index,
                                        sourceId: a.source_id,
                                      });
                                    }}
                                  >
                                    {a.name}
                                  </ArticleCardRowStyled.Link>
                                </ArticleCardRowStyled.Title>
                                <ArticleCardRowStyled.Content>{a.content_text}</ArticleCardRowStyled.Content>
                              </ArticleCardRowStyled.Right>
                            </ArticleCardRowStyled.Root>
                          ))}
                        </RowStyled>
                      )}
                      <PageControlsStyled>
                        <div>
                          <label>Results Per Page</label>
                          <Select.Root
                            defaultValue={String(defaultItemsPerPage)}
                            onValueChange={(v) =>
                              onResultsPerPageChange({
                                numItems: Number(v),
                              })
                            }
                          >
                            <SelectStyled.Trigger>
                              <SelectStyled.SelectValue />
                              <SelectStyled.Icon />
                            </SelectStyled.Trigger>
                            <SelectStyled.Content>
                              <SelectStyled.Viewport>
                                <SelectStyled.Option value="24">
                                  <SelectStyled.OptionText>24</SelectStyled.OptionText>
                                </SelectStyled.Option>
    
                                <SelectStyled.Option value="48">
                                  <SelectStyled.OptionText>48</SelectStyled.OptionText>
                                </SelectStyled.Option>
    
                                <SelectStyled.Option value="64">
                                  <SelectStyled.OptionText>64</SelectStyled.OptionText>
                                </SelectStyled.Option>
                              </SelectStyled.Viewport>
                            </SelectStyled.Content>
                          </Select.Root>
                        </div>
                        <PaginationStyled.Root
                          currentPage={page}
                          defaultCurrentPage={1}
                          totalPages={totalPages}
                          onPageChange={(v) =>
                            onPageNumberChange({
                              page: v,
                            })
                          }
                        >
                          <PaginationStyled.PrevPage onClick={(e) => e.preventDefault()}>
                            <ArrowLeftIcon />
                          </PaginationStyled.PrevPage>
                          <PaginationStyled.Pages>
                            {(pagination) =>
                              Pagination.paginationLayout(pagination, {}).map(({ page, type }) =>
                                type === 'page' ? (
                                  <PaginationStyled.Page
                                    key={page}
                                    aria-label={`Page ${page}`}
                                    page={page}
                                    onClick={(e) => e.preventDefault()}
                                  >
                                    {page}
                                  </PaginationStyled.Page>
                                ) : (
                                  <span key={type}>...</span>
                                ),
                              )
                            }
                          </PaginationStyled.Pages>
                          <PaginationStyled.NextPage onClick={(e) => e.preventDefault()}>
                            <ArrowRightIcon />
                          </PaginationStyled.NextPage>
                        </PaginationStyled.Root>
                      </PageControlsStyled>
                    </SearchResultsLayout.RightArea>
                  </>
                )}
                {totalItems <= 0 && !isFetching && (
                  <SearchResultsLayout.NoResults>
                    <h3>0 Results</h3>
                  </SearchResultsLayout.NoResults>
                )}
              </SearchResultsLayout.MainArea>
            </SearchResultsLayout.Wrapper>
          )}
        </>
      );
    };
    const SearchResultsWithLayoutOptionWidget = widget(SearchResultsWithLayoutOptionComponent, WidgetDataType.SEARCH_RESULTS, 'content');
    export default SearchResultsWithLayoutOptionWidget;
    // styled.js
    import { keyframes, styled } from 'styled-components';
    
    import {
      AccordionFacets,
      ArticleCard,
      Breadcrumb,
      CardViewSwitcher,
      FacetItem,
      Pagination,
      SearchResultsAccordionFacets,
      Select,
      SortSelect,
      theme,
    } from '@sitecore-search/ui';
    
    const ArticleCardRowRootStyled = styled(ArticleCard.Root)`
      box-sizing: border-box;
      border: solid 1px ${theme.vars.palette.grey['400']};
      box-shadow: 2px 2px 4px ${theme.vars.palette.grey['400']};
      display: flex;
      height: 200px;
      width: 100%;
      padding: ${theme.vars.spacing.m};
      margin-bottom: ${theme.vars.spacing.m};
      cursor: pointer;
      &:focus-within {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
      &:hover {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
    `;
    const ArticleCardRowLeftStyled = styled.div`
      position: relative;
      height: 164px;
      width: 164px;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    `;
    const ArticleCardRowRightStyled = styled.div`
      box-sizing: border-box;
      padding-left: ${theme.vars.spacing.m};
      overflow: hidden;
      max-width: 80%;
    `;
    const ArticleCardRowImageStyled = styled(ArticleCard.Image)`
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    `;
    const ArticleCardRowTitleStyled = styled(ArticleCard.Title)`
      margin: 0 0 ${theme.vars.spacing.m};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize4.fontSize};
      font-weight: ${theme.vars.typography.fontSize4.fontWeight};
      line-height: ${theme.vars.typography.fontSize4.lineHeight};
    `;
    const ArticleCardRowContentStyled = styled(ArticleCard.Content)`
      margin: 0;
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      font-weight: ${theme.vars.typography.fontWeight};
      line-height: ${theme.vars.typography.lineHeight};
      color: ${theme.vars.palette.primary.main};
    `;
    const ArticleCardRowIdStyled = styled(ArticleCard.Id)`
      font-family: ${theme.vars.typography.fontFamilySystem};
      color: ${theme.vars.palette.primary.main};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
    `;
    const ArticleCardRowLinkStyled = styled.a`
      text-decoration: none;
      color: ${theme.vars.palette.primary.main};
      font-size: ${theme.vars.typography.fontSize4.fontSize};
      &:hover {
        text-decoration: none;
      }
      &:focus {
        text-decoration: none;
      }
    `;
    export const ArticleCardRowStyled = {
      Link: ArticleCardRowLinkStyled,
      Id: ArticleCardRowIdStyled,
      Content: ArticleCardRowContentStyled,
      Image: ArticleCardRowImageStyled,
      Title: ArticleCardRowTitleStyled,
      Root: ArticleCardRowRootStyled,
      Left: ArticleCardRowLeftStyled,
      Right: ArticleCardRowRightStyled,
    };
    const CardViewSwitcherRoot = styled(CardViewSwitcher.Root)`
      display: inline-flex;
      background-color: ${theme.vars.palette.common.white};
    `;
    const CardViewSwitcherItem = styled(CardViewSwitcher.Item)`
      all: unset;
      align-items: center;
      background-color: ${theme.vars.palette.common.white};
      color: ${theme.vars.palette.grey[500]};
      display: flex;
      height: 30px;
      justify-content: center;
      margin-left: ${theme.vars.spacing.s};
      width: 30px;
    
      &:first-child {
        margin-left: 0;
      }
    
      &:hover {
        background-color: ${theme.vars.palette.primary.light};
      }
      &[data-state='on'] {
        background-color: ${theme.vars.palette.primary.main};
        color: ${theme.vars.palette.primary.contrastText};
      }
    
      &:focus {
        position: relative;
        border: solid 1px ${theme.vars.palette.primary.main};
      }
    `;
    export const CardViewSwitcherStyled = {
      Root: CardViewSwitcherRoot,
      Item: CardViewSwitcherItem,
    };
    export const RowStyled = styled.div`
      width: 100%;
      display: block;
    `;
    const BreadcrumbRootStyled = styled(Breadcrumb.Root)`
      width: 100%;
      display: inline-block;
    `;
    const BreadcrumbListStyled = styled(Breadcrumb.List)`
      list-style: none;
      padding: 0;
    `;
    const BreadcrumbItemStyled = styled(Breadcrumb.Item)`
      display: inline;
      color: ${theme.vars.palette.primary.main};
      margin-left: ${theme.vars.spacing.xs};
    `;
    const BreadcrumbNavigationStyled = styled(Breadcrumb.Navigation)``;
    const BreadcrumbLinkStyled = styled(Breadcrumb.Link)`
      color: ${theme.vars.palette.primary.main};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      text-decoration: none;
    `;
    const BreadcrumbSeparatorStyled = styled(Breadcrumb.Separator)``;
    export const BreadcrumbStyled = {
      Root: BreadcrumbRootStyled,
      List: BreadcrumbListStyled,
      Item: BreadcrumbItemStyled,
      Navigation: BreadcrumbNavigationStyled,
      Link: BreadcrumbLinkStyled,
      Separator: BreadcrumbSeparatorStyled,
    };
    const selectTriggerStyle = `
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: ${theme.vars.spacing.xs};
      background-color: transparent;
      height: 40px;
      padding: ${theme.vars.spacing.xs} ${theme.vars.spacing.m};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      color: ${theme.vars.palette.primary.main};
      border: none;
      &:focus {
        outline: none;
      }
    `;
    const SortSelectTriggerStyled = styled(SortSelect.Trigger)`
      ${selectTriggerStyle}
    `;
    const GenericSelectTriggerStyled = styled(Select.Trigger)`
      ${selectTriggerStyle}
    `;
    const contentSelectStyle = `
      background-color: ${theme.vars.palette.primary.contrastText};
      position: relative;
      overflow: hidden;
      color: ${theme.vars.palette.primary.main};
      box-shadow: 2px 2px 4px ${theme.vars.palette.grey['400']};
      position: absolute;
      top: 30px;
      z-index: 100;
      &:focus-within {
        border-color: ${theme.vars.palette.primary.dark};
      }
    `;
    const SortSelectContentStyled = styled(SortSelect.Content)`
      ${contentSelectStyle}
    `;
    const GenericSelectContentStyled = styled(Select.SelectContent)`
      ${contentSelectStyle}
    `;
    const viewportSelectStyles = `
      padding: ${theme.vars.spacing.xs};
      z-index: 50000;
    `;
    const SortSelectViewportStyled = styled(SortSelect.Viewport)`
      ${viewportSelectStyles}
    `;
    const GenericSelectViewportStyled = styled(Select.Viewport)`
      ${viewportSelectStyles}
    `;
    const optionSelectStyles = `
      display: flex;
      align-items: center;
      line-height: 1;
      cursor: pointer;
      user-select: none;
      white-space: nowrap;
      height: 25px;
      padding: 0 ${theme.vars.spacing.xs};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      color: ${theme.vars.palette.primary.main};
      position: relative;
      border-radius: 0px;
    
      &[data-highlighted] {
        border-radius: 0;
        background-color: ${theme.vars.palette.primary.dark};
        color: ${theme.vars.palette.primary.contrastText};
      }
      &[data-disabled] {
        color: ${theme.vars.palette.grey['800']};
        font-style: italic;
      }
    `;
    const SortSelectOptionStyled = styled(SortSelect.Option)`
      ${optionSelectStyles}
    `;
    const GenericSelectOptionStyled = styled(Select.SelectItem)`
      ${optionSelectStyles}
    `;
    const SortSelectValueStyled = styled(SortSelect.SelectValue)`
      color: ${theme.vars.palette.primary.main};
    `;
    const GenericSelectValueStyled = styled(Select.SelectValue)`
      color: ${theme.vars.palette.primary.main};
    `;
    const SortSelectIconStyled = styled(SortSelect.Icon)``;
    const GenericSelectIconStyled = styled(Select.Icon)``;
    const SortSelectOptionTextStyled = styled(SortSelect.OptionText)``;
    const GenericSelectOptionTextStyled = styled(SortSelect.OptionText)``;
    export const SortSelectStyled = {
      Trigger: SortSelectTriggerStyled,
      Content: SortSelectContentStyled,
      Viewport: SortSelectViewportStyled,
      Option: SortSelectOptionStyled,
      SelectValue: SortSelectValueStyled,
      OptionText: SortSelectOptionTextStyled,
      Icon: SortSelectIconStyled,
    };
    export const SelectStyled = {
      Trigger: GenericSelectTriggerStyled,
      Icon: GenericSelectIconStyled,
      SelectValue: GenericSelectValueStyled,
      Content: GenericSelectContentStyled,
      Viewport: GenericSelectViewportStyled,
      Option: GenericSelectOptionStyled,
      OptionText: GenericSelectOptionTextStyled,
    };
    const ArticleRootStyled = styled(ArticleCard.Root)`
      border: solid 1px ${theme.vars.palette.grey['400']};
      box-shadow: 2px 2px 4px ${theme.vars.palette.grey['400']};
      width: 180px;
      padding: ${theme.vars.spacing.m};
      cursor: pointer;
      display: block;
      &:focus-within {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
      &:hover {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
    `;
    const ArticleImageStyled = styled(ArticleCard.Image)`
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    `;
    const ImageWrapperStyled = styled.div`
      margin: auto auto 10px;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    `;
    const ArticleTitleStyled = styled(ArticleCard.Title)`
      margin: 0 0 ${theme.vars.spacing.m};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize3.fontSize};
      font-weight: ${theme.vars.typography.fontSize3.fontWeight};
      line-height: ${theme.vars.typography.fontSize3.lineHeight};
    `;
    const ArticleContentStyled = styled(ArticleCard.Content)`
      margin: 0;
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      font-weight: ${theme.vars.typography.fontWeight};
      line-height: ${theme.vars.typography.lineHeight};
      color: ${theme.vars.palette.primary.main};
    `;
    const ArticleIdStyled = styled(ArticleCard.Id)`
      font-family: ${theme.vars.typography.fontFamilySystem};
      color: ${theme.vars.palette.primary.main};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
    `;
    const ArticleLinkStyled = styled.a`
      text-decoration: none;
      color: ${theme.vars.palette.primary.main};
      font-size: ${theme.vars.typography.fontSize4.fontSize};
      &:hover {
        text-decoration: none;
      }
      &:focus {
        text-decoration: none;
      }
    `;
    export const ArticleCardStyled = {
      Link: ArticleLinkStyled,
      Id: ArticleIdStyled,
      Content: ArticleContentStyled,
      ImageWrapper: ImageWrapperStyled,
      Image: ArticleImageStyled,
      Title: ArticleTitleStyled,
      Root: ArticleRootStyled,
    };
    const AccordionItemCheckboxStyled = styled(AccordionFacets.ItemCheckbox)`
      all: unset;
      background-color: white;
      width: 20px;
      height: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid #ccc;
      border-radius: 4px;
      cursor: pointer;
      &[data-state='checked'] {
        color: ${theme.vars.palette.primary.contrastText};
        background-color: ${theme.vars.palette.primary.main};
      }
    
      &:focus {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
    `;
    const AccordionItemToggleStyled = styled(AccordionFacets.ItemToggle)`
      all: unset;
      width: 40px;
      height: 35px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      margin-right: ${theme.vars.spacing.s};
    
      &:focus {
        border: solid 1px ${theme.vars.palette.primary.main};
      }
    
      &[data-state='on'] {
        background-color: ${theme.vars.palette.primary.main};
        color: ${theme.vars.palette.primary.contrastText};
      }
    `;
    const AccordionItemCheckboxIndicatorStyled = styled(AccordionFacets.ItemCheckboxIndicator)`
      color: ${theme.vars.palette.primary.contrastText};
      width: 15px;
      height: 15px;
    `;
    const AccordionValueListStyled = styled(AccordionFacets.ValueList)`
      list-style: none;
      li {
        padding: ${theme.vars.spacing.xs} 0;
        font-family: ${theme.vars.typography.fontFamilySystem};
        font-size: ${theme.vars.typography.fontSize1.fontSize};
      }
    
      &[data-orientation='horizontal'] {
        display: flex;
        flex-direction: row;
      }
    `;
    const AccordionItemCheckboxLabelStyled = styled(AccordionFacets.ItemLabel)`
      padding-left: ${theme.vars.spacing.xs};
      color: ${theme.vars.palette.primary.main};
    `;
    const AccordionItemStyled = styled(FacetItem)`
      display: flex;
      align-items: center;
    `;
    const AccordionHeaderStyled = styled(AccordionFacets.Header)`
      display: flex;
      margin-top: ${theme.vars.spacing.s};
      margin-bottom: ${theme.vars.spacing.s};
    `;
    const AccordionTriggerStyled = styled(AccordionFacets.Trigger)`
      align-items: center;
      color: ${theme.vars.palette.primary.main};
      display: flex;
      font-size: ${theme.vars.typography.fontSize2.fontSize};
      height: 45px;
      justify-content: space-between;
      line-height: 1;
      padding: 0 ${theme.vars.spacing.m};
      flex: 1 1 0;
      background: none;
      border: none;
    `;
    const AccordionFacetsFacetStyled = styled(AccordionFacets.Facet)`
      border-bottom: solid 1px ${theme.vars.palette.grey['800']};
    `;
    const AccordionFacetsRootStyled = styled(SearchResultsAccordionFacets)``;
    export const AccordionFacetsStyled = {
      Trigger: AccordionTriggerStyled,
      Header: AccordionHeaderStyled,
      Item: AccordionItemStyled,
      ItemCheckboxLabel: AccordionItemCheckboxLabelStyled,
      ValueList: AccordionValueListStyled,
      ItemCheckboxIndicator: AccordionItemCheckboxIndicatorStyled,
      ItemToggle: AccordionItemToggleStyled,
      ItemCheckbox: AccordionItemCheckboxStyled,
      Facet: AccordionFacetsFacetStyled,
      Root: AccordionFacetsRootStyled,
    };
    const paginationLinkStyle = `
      cursor: pointer;
      margin: 0 5px;
    
      &[data-current='true'] {
        color: gray;
        pointer-events: none;
        text-decoration-line: none;
      }
    `;
    const paginationNavigationLinkStyle = `
      ${paginationLinkStyle}
      &[data-current='true'] {
        display: none;
      }
    `;
    const PaginationRootStyled = styled(Pagination.Root)`
      color: ${theme.vars.palette.primary.main};
      font-family: ${theme.vars.typography.fontFamilySystem};
      margin-top: ${theme.vars.spacing.m};
    `;
    const PaginationPrevPageStyled = styled(Pagination.PrevPage)`
      display: inline;
      ${paginationNavigationLinkStyle}
    `;
    const PaginationNextPageStyled = styled(Pagination.NextPage)`
      display: inline;
      ${paginationNavigationLinkStyle}
    `;
    const PaginationFirstPageStyled = styled(Pagination.FirstPage)`
      font-size: ${theme.vars.typography.fontSize2.fontSize};
      ${paginationNavigationLinkStyle}
    `;
    const PaginationLastPageStyled = styled(Pagination.LastPage)`
      font-size: ${theme.vars.typography.fontSize2.fontSize};
      ${paginationLinkStyle}
    `;
    const PaginationPageStyled = styled(Pagination.Page)`
      cursor: pointer;
      ${paginationLinkStyle}
    `;
    const PaginationPagesStyled = styled(Pagination.Pages)`
      display: inline;
    `;
    export const PaginationStyled = {
      Root: PaginationRootStyled,
      PrevPage: PaginationPrevPageStyled,
      NextPage: PaginationNextPageStyled,
      FirstPage: PaginationFirstPageStyled,
      LastPage: PaginationLastPageStyled,
      Page: PaginationPageStyled,
      Pages: PaginationPagesStyled,
    };
    const Wrapper = styled.div``;
    const MainArea = styled.div`
      display: flex;
      max-width: 100%;
      position: relative;
    `;
    const LeftArea = styled.section`
      position: relative;
      flex: 1 1;
      margin-right: ${theme.vars.spacing.l};
    `;
    const RightArea = styled.section`
      display: flex;
      flex-direction: column;
      flex: 4 1;
    `;
    const RightTopArea = styled.section`
      display: flex;
      justify-content: space-between;
    `;
    export const GridStyled = styled.div`
      width: 100%;
      display: grid;
      grid-gap: ${theme.vars.spacing.m};
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      grid-auto-flow: row;
    `;
    const ClearFilters = styled.button`
      background: none;
      border: none;
      color: ${theme.vars.palette.primary.main};
      &:hover {
        text-decoration: underline;
        cursor: pointer;
      }
    `;
    const SelectedFiltersList = styled.ul`
      width: 100%;
      list-style: none;
      padding-left: ${theme.vars.spacing.s};
    `;
    const SelectedFiltersListItem = styled.li`
      display: flex;
      justify-content: space-between;
      padding: ${theme.vars.spacing.xs} 0 ${theme.vars.spacing.xs} 0;
    `;
    const SelectedFiltersListItemText = styled.span`
      color: ${theme.vars.palette.primary.main};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
    `;
    const SelectedFiltersListItemButton = styled.button`
      background: none;
      border: none;
      color: ${theme.vars.palette.primary.main};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      cursor: pointer;
    `;
    export const PageControlsStyled = styled.div`
      display: flex;
      justify-content: space-between;
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
    `;
    export const QuerySummaryStyled = styled.div`
      color: ${theme.vars.palette.primary.main};
      font-family: ${theme.vars.typography.fontFamilySystem};
      font-size: ${theme.vars.typography.fontSize1.fontSize};
      font-weight: bold;
      margin: auto 0;
    `;
    export const LoaderContainer = styled.div`
      align-items: center;
      display: flex;
      position: absolute;
      left: 0;
      right: 0;
      height: 100%;
      background-color: ${theme.vars.palette.common.white};
      opacity: 0.5;
      z-index: 100;
    `;
    const Rotate = keyframes`
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    `;
    export const LoaderAnimation = styled.svg`
      animation: ${Rotate} 2s linear infinite;
      display: block;
      fill: ${theme.vars.palette.primary.main};
      height: 50px;
      margin: auto;
      width: 50px;
    `;
    export const FiltersStyled = {
      ClearFilters,
      SelectedFiltersList,
      SelectedFiltersListItem,
      SelectedFiltersListItemText,
      SelectedFiltersListItemButton,
    };
    export const NoResults = styled.div`
      display: flex;
      justify-content: center;
      width: 100%;
    `;
    export const SearchResultsLayout = {
      Wrapper,
      MainArea,
      NoResults,
      LeftArea,
      RightArea,
      RightTopArea,
    };

CSS styled component

CSS styled widget templates use UI primitives which are styled using a styles.css. To customize, you can edit the component and CSS.

TS CLI

With the Sitecore Search CLI you can replicate all necessary files of the widget template in your React project. The following procedure includes instructions for installing the CLI in your project and specifying the location where widget components are created.

If you have previously installed the CLI in your project and have a location defined in a file called .sc-search-settings.json, you can proceed directly to step 3.

To install the UI Components CLI in your project:

  1. In your project, to install the development dependency, open the terminal of your IDE or command prompt and run the following command:

    npm install @radix-ui/react-icons@^1.3.0
  2. To create the widget component in index.tsx and styles.css files, go to the location you defined in the .sc-search-settings.json file then open the terminal of your IDE or command prompt and run the following command:

    npx sc-search new-widget --language typescript --template search-results-row-grid --entity content --styling css

TS manual

When you manually create widget templates in your React project, you need to ensure that you install any required dependencies and create all necessary files.

To create the widget component using this widget template in your project:

  1. In your project, to install the development dependency, open the terminal of your IDE or command prompt and run the following command:

    npm install @radix-ui/react-icons@^1.3.0
  2. In a folder for the widget component, create files called index.tsx and styles.css then in respective files, paste following code blocks.

    // index.tsx
    import { useState } from 'react';
    
    import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, GridIcon, ListBulletIcon } from '@radix-ui/react-icons';
    import { Presence } from '@radix-ui/react-presence';
    import type { SearchResultsInitialState, SearchResultsStoreState } from '@sitecore-search/react';
    import { WidgetDataType, useSearchResults, useSearchResultsSelectedFilters, widget } from '@sitecore-search/react';
    import {
      AccordionFacets,
      ArticleCard,
      CardViewSwitcher,
      FacetItem,
      Pagination,
      SearchResultsAccordionFacets,
      Select,
      SortSelect,
    } from '@sitecore-search/ui';
    
    import './styles.css';
    
    type ArticleModel = {
      id: string;
      type?: string;
      title?: string;
      name?: string;
      subtitle?: string;
      url?: string;
      description?: string;
      content_text?: string;
      image_url?: string;
      source_id?: string;
    };
    type ArticlesSearchResultsProps = {
      defaultSortType?: SearchResultsStoreState['sortType'];
      defaultPage?: SearchResultsStoreState['page'];
      defaultItemsPerPage?: SearchResultsStoreState['itemsPerPage'];
      defaultKeyphrase?: SearchResultsStoreState['keyphrase'];
    };
    type InitialState = SearchResultsInitialState<'itemsPerPage' | 'keyphrase' | 'page' | 'sortType'>;
    const buildRangeLabel = (min: number | undefined, max: number | undefined): string => {
      return typeof min === 'undefined' ? `< $${max}` : typeof max === 'undefined' ? ` > $${min}` : `$${min} - $${max}`;
    };
    const buildFacetLabel = (selectedFacet: any) => {
      if ('min' in selectedFacet || 'max' in selectedFacet) {
        return `${selectedFacet.facetLabel}: ${buildRangeLabel(selectedFacet.min, selectedFacet.max)}`;
      }
      return `${selectedFacet.facetLabel}: ${selectedFacet.valueLabel}`;
    };
    export const SearchResultsWithLayoutOptionComponent = ({
      defaultSortType = 'featured_desc',
      defaultPage = 1,
      defaultKeyphrase = '',
      defaultItemsPerPage = 24,
    }: ArticlesSearchResultsProps) => {
      const {
        widgetRef,
        actions: {
          onResultsPerPageChange,
          onPageNumberChange,
          onItemClick,
          onRemoveFilter,
          onSortChange,
          onFacetClick,
          onClearFilters,
        },
        state: { sortType, page, itemsPerPage },
        queryResult: {
          isLoading,
          isFetching,
          data: {
            total_item: totalItems = 0,
            sort: { choices: sortChoices = [] } = {},
            facet: facets = [],
            content: articles = [],
          } = {},
        },
      } = useSearchResults<ArticleModel, InitialState>({
        state: {
          sortType: defaultSortType,
          page: defaultPage,
          itemsPerPage: defaultItemsPerPage,
          keyphrase: defaultKeyphrase,
        },
      });
      const totalPages = Math.ceil(totalItems / itemsPerPage);
      const selectedSortIndex = sortChoices.findIndex((s) => s.name === sortType);
      const selectedFacetsFromApi = useSearchResultsSelectedFilters();
      const defaultCardView = CardViewSwitcher.CARD_VIEW_LIST;
      const [dir, setDir] = useState(defaultCardView);
      const onToggle = (value = defaultCardView) => setDir(value);
      return (
        <>
          {isLoading && (
            <div className="sitecore-loader-container">
              <Presence present={isLoading}>
                <svg
                  aria-busy={isLoading}
                  aria-hidden={!isLoading}
                  focusable="false"
                  role="progressbar"
                  viewBox="0 0 20 20"
                  className="sitecore-loader-animation"
                >
                  <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                </svg>
              </Presence>
            </div>
          )}
          {!isLoading && (
            <div ref={widgetRef} className="sitecore-wrapper">
              <div className="sitecore-main-area">
                {isFetching && (
                  <div className="sitecore-loader-container">
                    <Presence present={true}>
                      <svg
                        aria-busy={true}
                        aria-hidden={false}
                        focusable="false"
                        role="progressbar"
                        viewBox="0 0 20 20"
                        className="sitecore-loader-animation"
                      >
                        <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                      </svg>
                    </Presence>
                  </div>
                )}
                {totalItems > 0 && (
                  <>
                    <section className="sitecore-left-area">
                      {selectedFacetsFromApi.length > 0 && (
                        <button onClick={onClearFilters} className="sitecore-clear-filters">
                          Clear Filters
                        </button>
                      )}
                      <ul className="sitecore-selected-filters-list">
                        {selectedFacetsFromApi.map((selectedFacet) => (
                          <li
                            key={`${selectedFacet.facetId}${selectedFacet.facetLabel}${selectedFacet.valueLabel}`}
                            className="sitecore-selected-filters-list-item"
                          >
                            <span className="sitecore-selected-filters-list-item-text">
                              {buildFacetLabel(selectedFacet)}
                            </span>
                            <button
                              onClick={() => onRemoveFilter(selectedFacet)}
                              className="sitecore-selected-filters-list-item-button"
                            >
                              X
                            </button>
                          </li>
                        ))}
                      </ul>
                      <SearchResultsAccordionFacets
                        defaultFacetTypesExpandedList={[]}
                        onFacetTypesExpandedListChange={() => {}}
                        onFacetValueClick={onFacetClick}
                        className="sitecore-accordion-facets-root"
                      >
                        {facets.map((f) => (
                          <AccordionFacets.Facet facetId={f.name} key={f.name} className="sitecore-accordion-facets-facet">
                            <AccordionFacets.Header className="sitecore-accordion-header">
                              <AccordionFacets.Trigger className="sitecore-accordion-trigger">
                                {f.label}
                              </AccordionFacets.Trigger>
                            </AccordionFacets.Header>
                            <AccordionFacets.Content>
                              <AccordionFacets.ValueList className="sitecore-accordion-value-list">
                                {f.value.map((v, index) => (
                                  <FacetItem
                                    {...{
                                      index,
                                      facetValueId: v.id,
                                    }}
                                    key={v.id}
                                    className="sitecore-accordion-item"
                                  >
                                    <AccordionFacets.ItemCheckbox className="sitecore-accordion-item-checkbox">
                                      <AccordionFacets.ItemCheckboxIndicator className="sitecore-accordion-item-checkbox-indicator">
                                        <CheckIcon />
                                      </AccordionFacets.ItemCheckboxIndicator>
                                    </AccordionFacets.ItemCheckbox>
                                    <AccordionFacets.ItemLabel className="sitecore-accordion-item-checkbox-label">
                                      {v.text} {v.count && `(${v.count})`}
                                    </AccordionFacets.ItemLabel>
                                  </FacetItem>
                                ))}
                              </AccordionFacets.ValueList>
                            </AccordionFacets.Content>
                          </AccordionFacets.Facet>
                        ))}
                      </SearchResultsAccordionFacets>
                    </section>
                    <section className="sitecore-right-area">
                      <section className="sitecore-right-top-area">
                        {totalItems && (
                          <div className="sitecore-query-summary">
                            <b>
                              Showing {itemsPerPage * (page - 1) + 1} - {itemsPerPage * (page - 1) + articles.length} of{' '}
                              {totalItems} results
                            </b>
                          </div>
                        )}
    
                        <div>
                          {/* Card View Switcher */}
                          <CardViewSwitcher.Root
                            onValueChange={onToggle}
                            defaultValue={defaultCardView}
                            className="sitecore-card-view-switcher-root"
                          >
                            <CardViewSwitcher.Item
                              value="grid"
                              aria-label="Grid View"
                              className="sitecore-card-view-switcher-item"
                            >
                              <GridIcon />
                            </CardViewSwitcher.Item>
                            <CardViewSwitcher.Item
                              value="list"
                              aria-label="List View"
                              className="sitecore-card-view-switcher-item"
                            >
                              <ListBulletIcon />
                            </CardViewSwitcher.Item>
                          </CardViewSwitcher.Root>
    
                          {/* Sort Select */}
                          <SortSelect.Root defaultValue={sortChoices[selectedSortIndex]?.name} onValueChange={onSortChange}>
                            <SortSelect.Trigger className="sitecore-sort-select-trigger">
                              <SortSelect.SelectValue className="sitecore-sort-select-value">
                                {selectedSortIndex > -1 ? sortChoices[selectedSortIndex].label : ''}
                              </SortSelect.SelectValue>
                              <SortSelect.Icon className="sitecore-sort-select-icon" />
                            </SortSelect.Trigger>
                            <SortSelect.Content className="sitecore-sort-select-content">
                              <SortSelect.Viewport className="sitecore-sort-select-viewport">
                                {sortChoices.map((option) => (
                                  <SortSelect.Option
                                    value={option}
                                    key={option.name}
                                    className="sitecore-sort-select-option"
                                  >
                                    <SortSelect.OptionText className="sitecore-sort-select-option-text">
                                      {option.label}
                                    </SortSelect.OptionText>
                                  </SortSelect.Option>
                                ))}
                              </SortSelect.Viewport>
                            </SortSelect.Content>
                          </SortSelect.Root>
                        </div>
                      </section>
    
                      {/* Results */}
                      {dir === CardViewSwitcher.CARD_VIEW_GRID ? (
                        <div className="sitecore-grid">
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id} className="sitecore-article-root">
                              <div className="sitecore-image-wrapper">
                                <ArticleCard.Image src={a.image_url} className="sitecore-article-image" />
                              </div>
                              <ArticleCard.Id className="sitecore-article-id">{a.id}</ArticleCard.Id>
                              <ArticleCard.Title className="sitecore-article-title">
                                <a
                                  href="#"
                                  onClick={(e) => {
                                    e.preventDefault();
                                    onItemClick({
                                      id: a.id,
                                      index,
                                      sourceId: a.source_id,
                                    });
                                  }}
                                  className="sitecore-article-link"
                                >
                                  {a.name}
                                </a>
                              </ArticleCard.Title>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      ) : (
                        <div className="sitecore-row">
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id} className="sitecore-article-card-row-root">
                              <div className="sitecore-article-card-row-left">
                                <ArticleCard.Image src={a.image_url} className="sitecore-article-card-row-image" />
                              </div>
                              <div className="sitecore-article-card-row-right">
                                <ArticleCard.Title className="sitecore-article-card-row-title">
                                  <a
                                    href="#"
                                    onClick={(e) => {
                                      e.preventDefault();
                                      onItemClick({
                                        id: a.id,
                                        index,
                                        sourceId: a.source_id,
                                      });
                                    }}
                                    className="sitecore-article-card-row-link"
                                  >
                                    {a.name}
                                  </a>
                                </ArticleCard.Title>
                                <ArticleCard.Content className="sitecore-article-card-row-content">
                                  {a.content_text}
                                </ArticleCard.Content>
                              </div>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      )}
                      <div className="sitecore-page-controls">
                        <div>
                          <label>Results Per Page</label>
                          <Select.Root
                            defaultValue={String(defaultItemsPerPage)}
                            onValueChange={(v) =>
                              onResultsPerPageChange({
                                numItems: Number(v),
                              })
                            }
                          >
                            <Select.Trigger className="sitecore-generic-select-trigger">
                              <Select.SelectValue className="sitecore-generic-select-value" />
                              <Select.Icon className="sitecore-generic-select-icon" />
                            </Select.Trigger>
                            <Select.SelectContent className="sitecore-generic-select-content">
                              <Select.Viewport className="sitecore-generic-select-viewport">
                                <Select.SelectItem value="24" className="sitecore-generic-select-option">
                                  <SortSelect.OptionText className="sitecore-generic-select-option-text">
                                    24
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="48" className="sitecore-generic-select-option">
                                  <SortSelect.OptionText className="sitecore-generic-select-option-text">
                                    48
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="64" className="sitecore-generic-select-option">
                                  <SortSelect.OptionText className="sitecore-generic-select-option-text">
                                    64
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
                              </Select.Viewport>
                            </Select.SelectContent>
                          </Select.Root>
                        </div>
                        <Pagination.Root
                          currentPage={page}
                          defaultCurrentPage={1}
                          totalPages={totalPages}
                          onPageChange={(v) =>
                            onPageNumberChange({
                              page: v,
                            })
                          }
                          className="sitecore-pagination-root"
                        >
                          <Pagination.PrevPage
                            onClick={(e) => e.preventDefault()}
                            className="sitecore-pagination-prev-page"
                          >
                            <ArrowLeftIcon />
                          </Pagination.PrevPage>
                          <Pagination.Pages className="sitecore-pagination-pages">
                            {(pagination) =>
                              Pagination.paginationLayout(pagination, {}).map(({ page, type }) =>
                                type === 'page' ? (
                                  <Pagination.Page
                                    key={page}
                                    aria-label={`Page ${page}`}
                                    page={page as number}
                                    onClick={(e) => e.preventDefault()}
                                    className="sitecore-pagination-page"
                                  >
                                    {page}
                                  </Pagination.Page>
                                ) : (
                                  <span key={type}>...</span>
                                ),
                              )
                            }
                          </Pagination.Pages>
                          <Pagination.NextPage
                            onClick={(e) => e.preventDefault()}
                            className="sitecore-pagination-next-page"
                          >
                            <ArrowRightIcon />
                          </Pagination.NextPage>
                        </Pagination.Root>
                      </div>
                    </section>
                  </>
                )}
                {totalItems <= 0 && !isFetching && (
                  <div className="sitecore-no-results">
                    <h3>0 Results</h3>
                  </div>
                )}
              </div>
            </div>
          )}
        </>
      );
    };
    const SearchResultsWithLayoutOptionWidget = widget(SearchResultsWithLayoutOptionComponent, WidgetDataType.SEARCH_RESULTS, 'content');
    export default SearchResultsWithLayoutOptionWidget;
    // styles.css
    @keyframes rotate-animation {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    
    /* ArticleCardRowRoot */
    .sitecore-article-card-row-root {
      box-sizing: border-box;
      border: solid 1px var(--sdc-palette-grey-400);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      display: flex;
      height: 200px;
      width: 100%;
      padding: var(--sdc-spacing-m);
      margin-bottom: var(--sdc-spacing-m);
      cursor: pointer;
    }
    
    .sitecore-article-card-row-root:focus-within {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-card-row-root:hover {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* ArticleCardRowLeft */
    .sitecore-article-card-row-left {
      position: relative;
      height: 164px;
      width: 164px;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleCardRowRight */
    .sitecore-article-card-row-right {
      box-sizing: border-box;
      padding-left: var(--sdc-spacing-m);
      overflow: hidden;
      max-width: 80%;
    }
    
    /* ArticleCardRowImage */
    .sitecore-article-card-row-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ArticleCardRowTitle */
    .sitecore-article-card-row-title {
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize4-fontSize);
      font-weight: var(--sdc-typography-fontSize4-fontWeight);
      line-height: var(--sdc-typography-fontSize4-lineHeight);
    }
    
    /* ArticleCardRowContent */
    .sitecore-article-card-row-content {
      margin: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: var(--sdc-typography-fontWeight);
      line-height: var(--sdc-typography-lineHeight);
      color: var(--sdc-palette-primary-main);
    }
    
    /* ArticleCardRowId */
    .sitecore-article-card-row-id {
      font-family: var(--sdc-typography-fontFamilySystem);
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* ArticleCardRowLink */
    .sitecore-article-card-row-link {
      text-decoration: none;
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize4-fontSize);
    }
    
    .sitecore-article-card-row-link:hover {
      text-decoration: none;
    }
    
    .sitecore-article-card-row-link:focus {
      text-decoration: none;
    }
    
    /* CardViewSwitcherRoot */
    .sitecore-card-view-switcher-root {
      display: inline-flex;
      background-color: var(--sdc-palette-common-white);
    }
    
    /* CardViewSwitcherItem */
    .sitecore-card-view-switcher-item {
      all: unset;
      align-items: center;
      background-color: var(--sdc-palette-common-white);
      color: var(--sdc-palette-grey-500);
      display: flex;
      height: 30px;
      justify-content: center;
      margin-left: var(--sdc-spacing-s);
      width: 30px;
    }
    
    .sitecore-card-view-switcher-item:first-child {
      margin-left: 0;
    }
    
    .sitecore-card-view-switcher-item:hover {
      background-color: var(--sdc-palette-primary-light);
    }
    
    .sitecore-card-view-switcher-item[data-state='on'] {
      background-color: var(--sdc-palette-primary-main);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-card-view-switcher-item:focus {
      position: relative;
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* Row */
    .sitecore-row {
      width: 100%;
      display: block;
    }
    
    /* BreadcrumbRoot */
    .sitecore-breadcrumb-root {
      width: 100%;
      display: inline-block;
    }
    
    /* BreadcrumbList */
    .sitecore-breadcrumb-list {
      list-style: none;
      padding: 0;
    }
    
    /* BreadcrumbItem */
    .sitecore-breadcrumb-item {
      display: inline;
      color: var(--sdc-palette-primary-main);
      margin-left: var(--sdc-spacing-xs);
    }
    
    /* BreadcrumbNavigation */
    .sitecore-breadcrumb-navigation {
    }
    
    /* BreadcrumbLink */
    .sitecore-breadcrumb-link {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      text-decoration: none;
    }
    
    /* BreadcrumbSeparator */
    .sitecore-breadcrumb-separator {
    }
    
    /* SortSelectTrigger */
    .sitecore-sort-select-trigger {
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: var(--sdc-spacing-xs);
      background-color: transparent;
      height: 40px;
      padding: var(--sdc-spacing-xs) var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      border: none;
    }
    
    .sitecore-sort-select-trigger:focus {
      outline: none;
    }
    
    /* GenericSelectTrigger */
    .sitecore-generic-select-trigger {
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: var(--sdc-spacing-xs);
      background-color: transparent;
      height: 40px;
      padding: var(--sdc-spacing-xs) var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      border: none;
    }
    
    .sitecore-generic-select-trigger:focus {
      outline: none;
    }
    
    /* SortSelectContent */
    .sitecore-sort-select-content {
      background-color: var(--sdc-palette-primary-contrastText);
      overflow: hidden;
      color: var(--sdc-palette-primary-main);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      position: absolute;
      top: 30px;
      z-index: 100;
    }
    
    .sitecore-sort-select-content:focus-within {
      border-color: var(--sdc-palette-primary-dark);
    }
    
    /* GenericSelectContent */
    .sitecore-generic-select-content {
      background-color: var(--sdc-palette-primary-contrastText);
      overflow: hidden;
      color: var(--sdc-palette-primary-main);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      position: absolute;
      top: 30px;
      z-index: 100;
    }
    
    .sitecore-generic-select-content:focus-within {
      border-color: var(--sdc-palette-primary-dark);
    }
    
    /* SortSelectViewport */
    .sitecore-sort-select-viewport {
      padding: var(--sdc-spacing-xs);
      z-index: 50000;
    }
    
    /* GenericSelectViewport */
    .sitecore-generic-select-viewport {
      padding: var(--sdc-spacing-xs);
      z-index: 50000;
    }
    
    /* SortSelectOption */
    .sitecore-sort-select-option {
      display: flex;
      align-items: center;
      line-height: 1;
      cursor: pointer;
      -webkit-user-select: none;
      -moz-user-select: none;
      user-select: none;
      white-space: nowrap;
      height: 25px;
      padding: 0 var(--sdc-spacing-xs);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      position: relative;
      border-radius: 0;
    }
    
    .sitecore-sort-select-option[data-highlighted] {
      border-radius: 0;
      background-color: var(--sdc-palette-primary-dark);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-sort-select-option[data-disabled] {
      color: var(--sdc-palette-grey-800);
      font-style: italic;
    }
    
    /* GenericSelectOption */
    .sitecore-generic-select-option {
      display: flex;
      align-items: center;
      line-height: 1;
      cursor: pointer;
      -webkit-user-select: none;
      -moz-user-select: none;
      user-select: none;
      white-space: nowrap;
      height: 25px;
      padding: 0 var(--sdc-spacing-xs);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      position: relative;
      border-radius: 0;
    }
    
    .sitecore-generic-select-option[data-highlighted] {
      border-radius: 0;
      background-color: var(--sdc-palette-primary-dark);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-generic-select-option[data-disabled] {
      color: var(--sdc-palette-grey-800);
      font-style: italic;
    }
    
    /* SortSelectValue */
    .sitecore-sort-select-value {
      color: var(--sdc-palette-primary-main);
    }
    
    /* GenericSelectValue */
    .sitecore-generic-select-value {
      color: var(--sdc-palette-primary-main);
    }
    
    /* SortSelectIcon */
    .sitecore-sort-select-icon {
    }
    
    /* GenericSelectIcon */
    .sitecore-generic-select-icon {
    }
    
    /* SortSelectOptionText */
    .sitecore-sort-select-option-text {
    }
    
    /* GenericSelectOptionText */
    .sitecore-generic-select-option-text {
    }
    
    /* ArticleRoot */
    .sitecore-article-root {
      border: solid 1px var(--sdc-palette-grey-400);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      width: 180px;
      padding: var(--sdc-spacing-m);
      cursor: pointer;
      display: block;
    }
    
    .sitecore-article-root:focus-within {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-root:hover {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* ArticleImage */
    .sitecore-article-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ImageWrapper */
    .sitecore-image-wrapper {
      margin: auto auto 10px;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleTitle */
    .sitecore-article-title {
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize3-fontSize);
      font-weight: var(--sdc-typography-fontSize3-fontWeight);
      line-height: var(--sdc-typography-fontSize3-lineHeight);
    }
    
    /* ArticleContent */
    .sitecore-article-content {
      margin: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: var(--sdc-typography-fontWeight);
      line-height: var(--sdc-typography-lineHeight);
      color: var(--sdc-palette-primary-main);
    }
    
    /* ArticleId */
    .sitecore-article-id {
      font-family: var(--sdc-typography-fontFamilySystem);
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* ArticleLink */
    .sitecore-article-link {
      text-decoration: none;
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize4-fontSize);
    }
    
    .sitecore-article-link:hover {
      text-decoration: none;
    }
    
    .sitecore-article-link:focus {
      text-decoration: none;
    }
    
    /* AccordionItemCheckbox */
    .sitecore-accordion-item-checkbox {
      all: unset;
      background-color: white;
      width: 20px;
      height: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid #ccc;
      border-radius: 4px;
      cursor: pointer;
    }
    
    .sitecore-accordion-item-checkbox[data-state='checked'] {
      color: var(--sdc-palette-primary-contrastText);
      background-color: var(--sdc-palette-primary-main);
    }
    
    .sitecore-accordion-item-checkbox:focus {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* AccordionItemToggle */
    .sitecore-accordion-item-toggle {
      all: unset;
      width: 40px;
      height: 35px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: var(--sdc-typography-fontSize1-fontSize);
      margin-right: var(--sdc-spacing-s);
    }
    
    .sitecore-accordion-item-toggle:focus {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-accordion-item-toggle[data-state='on'] {
      background-color: var(--sdc-palette-primary-main);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    /* AccordionItemCheckboxIndicator */
    .sitecore-accordion-item-checkbox-indicator {
      color: var(--sdc-palette-primary-contrastText);
      width: 15px;
      height: 15px;
    }
    
    /* AccordionValueList */
    .sitecore-accordion-value-list {
      list-style: none;
    }
    
    .sitecore-accordion-value-list li {
      padding: var(--sdc-spacing-xs) 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    .sitecore-accordion-value-list[data-orientation='horizontal'] {
      display: flex;
      flex-direction: row;
    }
    
    /* AccordionItemCheckboxLabel */
    .sitecore-accordion-item-checkbox-label {
      padding-left: var(--sdc-spacing-xs);
      color: var(--sdc-palette-primary-main);
    }
    
    /* AccordionItem */
    .sitecore-accordion-item {
      display: flex;
      align-items: center;
    }
    
    /* AccordionHeader */
    .sitecore-accordion-header {
      display: flex;
      margin-top: var(--sdc-spacing-s);
      margin-bottom: var(--sdc-spacing-s);
    }
    
    /* AccordionTrigger */
    .sitecore-accordion-trigger {
      align-items: center;
      color: var(--sdc-palette-primary-main);
      display: flex;
      font-size: var(--sdc-typography-fontSize2-fontSize);
      height: 45px;
      justify-content: space-between;
      line-height: 1;
      padding: 0 var(--sdc-spacing-m);
      flex: 1 1 0;
      background: none;
      border: none;
    }
    
    /* AccordionFacetsFacet */
    .sitecore-accordion-facets-facet {
      border-bottom: solid 1px var(--sdc-palette-grey-800);
    }
    
    /* AccordionFacetsRoot */
    .sitecore-accordion-facets-root {
    }
    
    /* PaginationRoot */
    .sitecore-pagination-root {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      margin-top: var(--sdc-spacing-m);
    }
    
    /* PaginationPrevPage */
    .sitecore-pagination-prev-page {
      display: inline;
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-prev-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationNextPage */
    .sitecore-pagination-next-page {
      display: inline;
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-next-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationFirstPage */
    .sitecore-pagination-first-page {
      font-size: var(--sdc-typography-fontSize2-fontSize);
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-first-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationLastPage */
    .sitecore-pagination-last-page {
      font-size: var(--sdc-typography-fontSize2-fontSize);
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-last-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
    }
    
    /* PaginationPage */
    .sitecore-pagination-page {
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
    }
    
    /* PaginationPages */
    .sitecore-pagination-pages {
      display: inline;
    }
    
    /* Wrapper */
    .sitecore-wrapper {
    }
    
    /* MainArea */
    .sitecore-main-area {
      display: flex;
      max-width: 100%;
      position: relative;
    }
    
    /* LeftArea */
    .sitecore-left-area {
      position: relative;
      flex: 1 1;
      margin-right: var(--sdc-spacing-l);
    }
    
    /* RightArea */
    .sitecore-right-area {
      display: flex;
      flex-direction: column;
      flex: 4 1;
    }
    
    /* RightTopArea */
    .sitecore-right-top-area {
      display: flex;
      justify-content: space-between;
    }
    
    /* Grid */
    .sitecore-grid {
      width: 100%;
      display: grid;
      grid-gap: var(--sdc-spacing-m);
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      grid-auto-flow: row;
    }
    
    /* ClearFilters */
    .sitecore-clear-filters {
      background: none;
      border: none;
      color: var(--sdc-palette-primary-main);
    }
    
    .sitecore-clear-filters:hover {
      text-decoration: underline;
      cursor: pointer;
    }
    
    /* SelectedFiltersList */
    .sitecore-selected-filters-list {
      width: 100%;
      list-style: none;
      padding-left: var(--sdc-spacing-s);
    }
    
    /* SelectedFiltersListItem */
    .sitecore-selected-filters-list-item {
      display: flex;
      justify-content: space-between;
      padding: var(--sdc-spacing-xs) 0 var(--sdc-spacing-xs) 0;
    }
    
    /* SelectedFiltersListItemText */
    .sitecore-selected-filters-list-item-text {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* SelectedFiltersListItemButton */
    .sitecore-selected-filters-list-item-button {
      background: none;
      border: none;
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      cursor: pointer;
    }
    
    /* PageControls */
    .sitecore-page-controls {
      display: flex;
      justify-content: space-between;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* QuerySummary */
    .sitecore-query-summary {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: bold;
      margin: auto 0;
    }
    
    /* LoaderContainer */
    .sitecore-loader-container {
      align-items: center;
      display: flex;
      position: absolute;
      left: 0;
      right: 0;
      height: 100%;
      background-color: var(--sdc-palette-common-white);
      opacity: 0.5;
      z-index: 100;
    }
    
    /* LoaderAnimation */
    .sitecore-loader-animation {
      animation: rotate-animation 2s linear infinite;
      display: block;
      fill: var(--sdc-palette-primary-main);
      height: 50px;
      margin: auto;
      width: 50px;
    }
    
    /* NoResults */
    .sitecore-no-results {
      display: flex;
      justify-content: center;
      width: 100%;
    }

JS CLI

With the Sitecore Search CLI you can replicate all necessary files of the widget template in your React project. The following procedure includes instructions for installing the CLI in your project and specifying the location where widget components are created.

If you have previously installed the CLI in your project and have a location defined in a file called .sc-search-settings.json, you can proceed directly to step 3.

To install the UI Components CLI in your project:

  1. To install the Search UI Components CLI, open the terminal of your IDE or command prompt and run the following command:

    npx install --save-dev @sitecore-search/cli
  2. To define the location of where widget components will be created, at the root of your project, in a file called .sc-search-settings.json, specify the location as shown in the following code block.

    {
        "components-path": "src-test/components"
    }
  3. To create the widget component in index.jsx and styles.css files, go to the location you defined in the .sc-search-settings.json file then open the terminal of your IDE or command prompt and run the following command:

    npx sc-search new-widget --language javascript --template search-results-row-grid --entity content --styling css

JS manual

When you manually create widget templates in your React project, you need to ensure that you install any required dependencies and create all necessary files.

To create the widget component using this widget template in your project:

  1. In your project, to install the development dependency, open the terminal of your IDE or command prompt and run the following command:

    npm install @radix-ui/react-icons@^1.3.0
  2. In a folder for the widget component, create files called index.jsx and styles.css then in respective files, paste following code blocks.

    // index.jsx
    import { useState } from 'react';
    
    import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, GridIcon, ListBulletIcon } from '@radix-ui/react-icons';
    import { Presence } from '@radix-ui/react-presence';
    import { WidgetDataType, useSearchResults, useSearchResultsSelectedFilters, widget } from '@sitecore-search/react';
    import {
      AccordionFacets,
      ArticleCard,
      CardViewSwitcher,
      FacetItem,
      Pagination,
      SearchResultsAccordionFacets,
      Select,
      SortSelect,
    } from '@sitecore-search/ui';
    
    import './styles.css';
    
    const buildRangeLabel = (min, max) => {
      return typeof min === 'undefined' ? `< $${max}` : typeof max === 'undefined' ? ` > $${min}` : `$${min} - $${max}`;
    };
    const buildFacetLabel = (selectedFacet) => {
      if ('min' in selectedFacet || 'max' in selectedFacet) {
        return `${selectedFacet.facetLabel}: ${buildRangeLabel(selectedFacet.min, selectedFacet.max)}`;
      }
      return `${selectedFacet.facetLabel}: ${selectedFacet.valueLabel}`;
    };
    export const SearchResultsWithLayoutOptionComponent = ({
      defaultSortType = 'featured_desc',
      defaultPage = 1,
      defaultKeyphrase = '',
      defaultItemsPerPage = 24,
    }) => {
      const {
        widgetRef,
        actions: {
          onResultsPerPageChange,
          onPageNumberChange,
          onItemClick,
          onRemoveFilter,
          onSortChange,
          onFacetClick,
          onClearFilters,
        },
        state: { sortType, page, itemsPerPage },
        queryResult: {
          isLoading,
          isFetching,
          data: {
            total_item: totalItems = 0,
            sort: { choices: sortChoices = [] } = {},
            facet: facets = [],
            content: articles = [],
          } = {},
        },
      } = useSearchResults({
        state: {
          sortType: defaultSortType,
          page: defaultPage,
          itemsPerPage: defaultItemsPerPage,
          keyphrase: defaultKeyphrase,
        },
      });
      const totalPages = Math.ceil(totalItems / itemsPerPage);
      const selectedSortIndex = sortChoices.findIndex((s) => s.name === sortType);
      const selectedFacetsFromApi = useSearchResultsSelectedFilters();
      const defaultCardView = CardViewSwitcher.CARD_VIEW_LIST;
      const [dir, setDir] = useState(defaultCardView);
      const onToggle = (value = defaultCardView) => setDir(value);
      return (
        <>
          {isLoading && (
            <div className="sitecore-loader-container">
              <Presence present={isLoading}>
                <svg
                  aria-busy={isLoading}
                  aria-hidden={!isLoading}
                  focusable="false"
                  role="progressbar"
                  viewBox="0 0 20 20"
                  className="sitecore-loader-animation"
                >
                  <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                </svg>
              </Presence>
            </div>
          )}
          {!isLoading && (
            <div ref={widgetRef} className="sitecore-wrapper">
              <div className="sitecore-main-area">
                {isFetching && (
                  <div className="sitecore-loader-container">
                    <Presence present={true}>
                      <svg
                        aria-busy={true}
                        aria-hidden={false}
                        focusable="false"
                        role="progressbar"
                        viewBox="0 0 20 20"
                        className="sitecore-loader-animation"
                      >
                        <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                      </svg>
                    </Presence>
                  </div>
                )}
                {totalItems > 0 && (
                  <>
                    <section className="sitecore-left-area">
                      {selectedFacetsFromApi.length > 0 && (
                        <button onClick={onClearFilters} className="sitecore-clear-filters">
                          Clear Filters
                        </button>
                      )}
                      <ul className="sitecore-selected-filters-list">
                        {selectedFacetsFromApi.map((selectedFacet) => (
                          <li
                            key={`${selectedFacet.facetId}${selectedFacet.facetLabel}${selectedFacet.valueLabel}`}
                            className="sitecore-selected-filters-list-item"
                          >
                            <span className="sitecore-selected-filters-list-item-text">
                              {buildFacetLabel(selectedFacet)}
                            </span>
                            <button
                              onClick={() => onRemoveFilter(selectedFacet)}
                              className="sitecore-selected-filters-list-item-button"
                            >
                              X
                            </button>
                          </li>
                        ))}
                      </ul>
                      <SearchResultsAccordionFacets
                        defaultFacetTypesExpandedList={[]}
                        onFacetTypesExpandedListChange={() => {}}
                        onFacetValueClick={onFacetClick}
                        className="sitecore-accordion-facets-root"
                      >
                        {facets.map((f) => (
                          <AccordionFacets.Facet facetId={f.name} key={f.name} className="sitecore-accordion-facets-facet">
                            <AccordionFacets.Header className="sitecore-accordion-header">
                              <AccordionFacets.Trigger className="sitecore-accordion-trigger">
                                {f.label}
                              </AccordionFacets.Trigger>
                            </AccordionFacets.Header>
                            <AccordionFacets.Content>
                              <AccordionFacets.ValueList className="sitecore-accordion-value-list">
                                {f.value.map((v, index) => (
                                  <FacetItem
                                    {...{
                                      index,
                                      facetValueId: v.id,
                                    }}
                                    key={v.id}
                                    className="sitecore-accordion-item"
                                  >
                                    <AccordionFacets.ItemCheckbox className="sitecore-accordion-item-checkbox">
                                      <AccordionFacets.ItemCheckboxIndicator className="sitecore-accordion-item-checkbox-indicator">
                                        <CheckIcon />
                                      </AccordionFacets.ItemCheckboxIndicator>
                                    </AccordionFacets.ItemCheckbox>
                                    <AccordionFacets.ItemLabel className="sitecore-accordion-item-checkbox-label">
                                      {v.text} {v.count && `(${v.count})`}
                                    </AccordionFacets.ItemLabel>
                                  </FacetItem>
                                ))}
                              </AccordionFacets.ValueList>
                            </AccordionFacets.Content>
                          </AccordionFacets.Facet>
                        ))}
                      </SearchResultsAccordionFacets>
                    </section>
                    <section className="sitecore-right-area">
                      <section className="sitecore-right-top-area">
                        {totalItems && (
                          <div className="sitecore-query-summary">
                            <b>
                              Showing {itemsPerPage * (page - 1) + 1} - {itemsPerPage * (page - 1) + articles.length} of{' '}
                              {totalItems} results
                            </b>
                          </div>
                        )}
    
                        <div>
                          {/* Card View Switcher */}
                          <CardViewSwitcher.Root
                            onValueChange={onToggle}
                            defaultValue={defaultCardView}
                            className="sitecore-card-view-switcher-root"
                          >
                            <CardViewSwitcher.Item
                              value="grid"
                              aria-label="Grid View"
                              className="sitecore-card-view-switcher-item"
                            >
                              <GridIcon />
                            </CardViewSwitcher.Item>
                            <CardViewSwitcher.Item
                              value="list"
                              aria-label="List View"
                              className="sitecore-card-view-switcher-item"
                            >
                              <ListBulletIcon />
                            </CardViewSwitcher.Item>
                          </CardViewSwitcher.Root>
    
                          {/* Sort Select */}
                          <SortSelect.Root defaultValue={sortChoices[selectedSortIndex]?.name} onValueChange={onSortChange}>
                            <SortSelect.Trigger className="sitecore-sort-select-trigger">
                              <SortSelect.SelectValue className="sitecore-sort-select-value">
                                {selectedSortIndex > -1 ? sortChoices[selectedSortIndex].label : ''}
                              </SortSelect.SelectValue>
                              <SortSelect.Icon className="sitecore-sort-select-icon" />
                            </SortSelect.Trigger>
                            <SortSelect.Content className="sitecore-sort-select-content">
                              <SortSelect.Viewport className="sitecore-sort-select-viewport">
                                {sortChoices.map((option) => (
                                  <SortSelect.Option
                                    value={option}
                                    key={option.name}
                                    className="sitecore-sort-select-option"
                                  >
                                    <SortSelect.OptionText className="sitecore-sort-select-option-text">
                                      {option.label}
                                    </SortSelect.OptionText>
                                  </SortSelect.Option>
                                ))}
                              </SortSelect.Viewport>
                            </SortSelect.Content>
                          </SortSelect.Root>
                        </div>
                      </section>
    
                      {/* Results */}
                      {dir === CardViewSwitcher.CARD_VIEW_GRID ? (
                        <div className="sitecore-grid">
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id} className="sitecore-article-root">
                              <div className="sitecore-image-wrapper">
                                <ArticleCard.Image src={a.image_url} className="sitecore-article-image" />
                              </div>
                              <ArticleCard.Id className="sitecore-article-id">{a.id}</ArticleCard.Id>
                              <ArticleCard.Title className="sitecore-article-title">
                                <a
                                  href="#"
                                  onClick={(e) => {
                                    e.preventDefault();
                                    onItemClick({
                                      id: a.id,
                                      index,
                                      sourceId: a.source_id,
                                    });
                                  }}
                                  className="sitecore-article-link"
                                >
                                  {a.name}
                                </a>
                              </ArticleCard.Title>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      ) : (
                        <div className="sitecore-row">
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id} className="sitecore-article-card-row-root">
                              <div className="sitecore-article-card-row-left">
                                <ArticleCard.Image src={a.image_url} className="sitecore-article-card-row-image" />
                              </div>
                              <div className="sitecore-article-card-row-right">
                                <ArticleCard.Title className="sitecore-article-card-row-title">
                                  <a
                                    href="#"
                                    onClick={(e) => {
                                      e.preventDefault();
                                      onItemClick({
                                        id: a.id,
                                        index,
                                        sourceId: a.source_id,
                                      });
                                    }}
                                    className="sitecore-article-card-row-link"
                                  >
                                    {a.name}
                                  </a>
                                </ArticleCard.Title>
                                <ArticleCard.Content className="sitecore-article-card-row-content">
                                  {a.content_text}
                                </ArticleCard.Content>
                              </div>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      )}
                      <div className="sitecore-page-controls">
                        <div>
                          <label>Results Per Page</label>
                          <Select.Root
                            defaultValue={String(defaultItemsPerPage)}
                            onValueChange={(v) =>
                              onResultsPerPageChange({
                                numItems: Number(v),
                              })
                            }
                          >
                            <Select.Trigger className="sitecore-generic-select-trigger">
                              <Select.SelectValue className="sitecore-generic-select-value" />
                              <Select.Icon className="sitecore-generic-select-icon" />
                            </Select.Trigger>
                            <Select.SelectContent className="sitecore-generic-select-content">
                              <Select.Viewport className="sitecore-generic-select-viewport">
                                <Select.SelectItem value="24" className="sitecore-generic-select-option">
                                  <SortSelect.OptionText className="sitecore-generic-select-option-text">
                                    24
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="48" className="sitecore-generic-select-option">
                                  <SortSelect.OptionText className="sitecore-generic-select-option-text">
                                    48
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="64" className="sitecore-generic-select-option">
                                  <SortSelect.OptionText className="sitecore-generic-select-option-text">
                                    64
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
                              </Select.Viewport>
                            </Select.SelectContent>
                          </Select.Root>
                        </div>
                        <Pagination.Root
                          currentPage={page}
                          defaultCurrentPage={1}
                          totalPages={totalPages}
                          onPageChange={(v) =>
                            onPageNumberChange({
                              page: v,
                            })
                          }
                          className="sitecore-pagination-root"
                        >
                          <Pagination.PrevPage
                            onClick={(e) => e.preventDefault()}
                            className="sitecore-pagination-prev-page"
                          >
                            <ArrowLeftIcon />
                          </Pagination.PrevPage>
                          <Pagination.Pages className="sitecore-pagination-pages">
                            {(pagination) =>
                              Pagination.paginationLayout(pagination, {}).map(({ page, type }) =>
                                type === 'page' ? (
                                  <Pagination.Page
                                    key={page}
                                    aria-label={`Page ${page}`}
                                    page={page}
                                    onClick={(e) => e.preventDefault()}
                                    className="sitecore-pagination-page"
                                  >
                                    {page}
                                  </Pagination.Page>
                                ) : (
                                  <span key={type}>...</span>
                                ),
                              )
                            }
                          </Pagination.Pages>
                          <Pagination.NextPage
                            onClick={(e) => e.preventDefault()}
                            className="sitecore-pagination-next-page"
                          >
                            <ArrowRightIcon />
                          </Pagination.NextPage>
                        </Pagination.Root>
                      </div>
                    </section>
                  </>
                )}
                {totalItems <= 0 && !isFetching && (
                  <div className="sitecore-no-results">
                    <h3>0 Results</h3>
                  </div>
                )}
              </div>
            </div>
          )}
        </>
      );
    };
    const SearchResultsWithLayoutOptionWidget = widget(SearchResultsWithLayoutOptionComponent, WidgetDataType.SEARCH_RESULTS, 'content');
    export default SearchResultsWithLayoutOptionWidget;
    // styles.css
    @keyframes rotate-animation {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    
    /* ArticleCardRowRoot */
    .sitecore-article-card-row-root {
      box-sizing: border-box;
      border: solid 1px var(--sdc-palette-grey-400);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      display: flex;
      height: 200px;
      width: 100%;
      padding: var(--sdc-spacing-m);
      margin-bottom: var(--sdc-spacing-m);
      cursor: pointer;
    }
    
    .sitecore-article-card-row-root:focus-within {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-card-row-root:hover {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* ArticleCardRowLeft */
    .sitecore-article-card-row-left {
      position: relative;
      height: 164px;
      width: 164px;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleCardRowRight */
    .sitecore-article-card-row-right {
      box-sizing: border-box;
      padding-left: var(--sdc-spacing-m);
      overflow: hidden;
      max-width: 80%;
    }
    
    /* ArticleCardRowImage */
    .sitecore-article-card-row-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ArticleCardRowTitle */
    .sitecore-article-card-row-title {
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize4-fontSize);
      font-weight: var(--sdc-typography-fontSize4-fontWeight);
      line-height: var(--sdc-typography-fontSize4-lineHeight);
    }
    
    /* ArticleCardRowContent */
    .sitecore-article-card-row-content {
      margin: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: var(--sdc-typography-fontWeight);
      line-height: var(--sdc-typography-lineHeight);
      color: var(--sdc-palette-primary-main);
    }
    
    /* ArticleCardRowId */
    .sitecore-article-card-row-id {
      font-family: var(--sdc-typography-fontFamilySystem);
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* ArticleCardRowLink */
    .sitecore-article-card-row-link {
      text-decoration: none;
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize4-fontSize);
    }
    
    .sitecore-article-card-row-link:hover {
      text-decoration: none;
    }
    
    .sitecore-article-card-row-link:focus {
      text-decoration: none;
    }
    
    /* CardViewSwitcherRoot */
    .sitecore-card-view-switcher-root {
      display: inline-flex;
      background-color: var(--sdc-palette-common-white);
    }
    
    /* CardViewSwitcherItem */
    .sitecore-card-view-switcher-item {
      all: unset;
      align-items: center;
      background-color: var(--sdc-palette-common-white);
      color: var(--sdc-palette-grey-500);
      display: flex;
      height: 30px;
      justify-content: center;
      margin-left: var(--sdc-spacing-s);
      width: 30px;
    }
    
    .sitecore-card-view-switcher-item:first-child {
      margin-left: 0;
    }
    
    .sitecore-card-view-switcher-item:hover {
      background-color: var(--sdc-palette-primary-light);
    }
    
    .sitecore-card-view-switcher-item[data-state='on'] {
      background-color: var(--sdc-palette-primary-main);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-card-view-switcher-item:focus {
      position: relative;
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* Row */
    .sitecore-row {
      width: 100%;
      display: block;
    }
    
    /* BreadcrumbRoot */
    .sitecore-breadcrumb-root {
      width: 100%;
      display: inline-block;
    }
    
    /* BreadcrumbList */
    .sitecore-breadcrumb-list {
      list-style: none;
      padding: 0;
    }
    
    /* BreadcrumbItem */
    .sitecore-breadcrumb-item {
      display: inline;
      color: var(--sdc-palette-primary-main);
      margin-left: var(--sdc-spacing-xs);
    }
    
    /* BreadcrumbNavigation */
    .sitecore-breadcrumb-navigation {
    }
    
    /* BreadcrumbLink */
    .sitecore-breadcrumb-link {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      text-decoration: none;
    }
    
    /* BreadcrumbSeparator */
    .sitecore-breadcrumb-separator {
    }
    
    /* SortSelectTrigger */
    .sitecore-sort-select-trigger {
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: var(--sdc-spacing-xs);
      background-color: transparent;
      height: 40px;
      padding: var(--sdc-spacing-xs) var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      border: none;
    }
    
    .sitecore-sort-select-trigger:focus {
      outline: none;
    }
    
    /* GenericSelectTrigger */
    .sitecore-generic-select-trigger {
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: var(--sdc-spacing-xs);
      background-color: transparent;
      height: 40px;
      padding: var(--sdc-spacing-xs) var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      border: none;
    }
    
    .sitecore-generic-select-trigger:focus {
      outline: none;
    }
    
    /* SortSelectContent */
    .sitecore-sort-select-content {
      background-color: var(--sdc-palette-primary-contrastText);
      overflow: hidden;
      color: var(--sdc-palette-primary-main);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      position: absolute;
      top: 30px;
      z-index: 100;
    }
    
    .sitecore-sort-select-content:focus-within {
      border-color: var(--sdc-palette-primary-dark);
    }
    
    /* GenericSelectContent */
    .sitecore-generic-select-content {
      background-color: var(--sdc-palette-primary-contrastText);
      overflow: hidden;
      color: var(--sdc-palette-primary-main);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      position: absolute;
      top: 30px;
      z-index: 100;
    }
    
    .sitecore-generic-select-content:focus-within {
      border-color: var(--sdc-palette-primary-dark);
    }
    
    /* SortSelectViewport */
    .sitecore-sort-select-viewport {
      padding: var(--sdc-spacing-xs);
      z-index: 50000;
    }
    
    /* GenericSelectViewport */
    .sitecore-generic-select-viewport {
      padding: var(--sdc-spacing-xs);
      z-index: 50000;
    }
    
    /* SortSelectOption */
    .sitecore-sort-select-option {
      display: flex;
      align-items: center;
      line-height: 1;
      cursor: pointer;
      -webkit-user-select: none;
      -moz-user-select: none;
      user-select: none;
      white-space: nowrap;
      height: 25px;
      padding: 0 var(--sdc-spacing-xs);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      position: relative;
      border-radius: 0;
    }
    
    .sitecore-sort-select-option[data-highlighted] {
      border-radius: 0;
      background-color: var(--sdc-palette-primary-dark);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-sort-select-option[data-disabled] {
      color: var(--sdc-palette-grey-800);
      font-style: italic;
    }
    
    /* GenericSelectOption */
    .sitecore-generic-select-option {
      display: flex;
      align-items: center;
      line-height: 1;
      cursor: pointer;
      -webkit-user-select: none;
      -moz-user-select: none;
      user-select: none;
      white-space: nowrap;
      height: 25px;
      padding: 0 var(--sdc-spacing-xs);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      position: relative;
      border-radius: 0;
    }
    
    .sitecore-generic-select-option[data-highlighted] {
      border-radius: 0;
      background-color: var(--sdc-palette-primary-dark);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-generic-select-option[data-disabled] {
      color: var(--sdc-palette-grey-800);
      font-style: italic;
    }
    
    /* SortSelectValue */
    .sitecore-sort-select-value {
      color: var(--sdc-palette-primary-main);
    }
    
    /* GenericSelectValue */
    .sitecore-generic-select-value {
      color: var(--sdc-palette-primary-main);
    }
    
    /* SortSelectIcon */
    .sitecore-sort-select-icon {
    }
    
    /* GenericSelectIcon */
    .sitecore-generic-select-icon {
    }
    
    /* SortSelectOptionText */
    .sitecore-sort-select-option-text {
    }
    
    /* GenericSelectOptionText */
    .sitecore-generic-select-option-text {
    }
    
    /* ArticleRoot */
    .sitecore-article-root {
      border: solid 1px var(--sdc-palette-grey-400);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      width: 180px;
      padding: var(--sdc-spacing-m);
      cursor: pointer;
      display: block;
    }
    
    .sitecore-article-root:focus-within {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-root:hover {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* ArticleImage */
    .sitecore-article-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ImageWrapper */
    .sitecore-image-wrapper {
      margin: auto auto 10px;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleTitle */
    .sitecore-article-title {
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize3-fontSize);
      font-weight: var(--sdc-typography-fontSize3-fontWeight);
      line-height: var(--sdc-typography-fontSize3-lineHeight);
    }
    
    /* ArticleContent */
    .sitecore-article-content {
      margin: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: var(--sdc-typography-fontWeight);
      line-height: var(--sdc-typography-lineHeight);
      color: var(--sdc-palette-primary-main);
    }
    
    /* ArticleId */
    .sitecore-article-id {
      font-family: var(--sdc-typography-fontFamilySystem);
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* ArticleLink */
    .sitecore-article-link {
      text-decoration: none;
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize4-fontSize);
    }
    
    .sitecore-article-link:hover {
      text-decoration: none;
    }
    
    .sitecore-article-link:focus {
      text-decoration: none;
    }
    
    /* AccordionItemCheckbox */
    .sitecore-accordion-item-checkbox {
      all: unset;
      background-color: white;
      width: 20px;
      height: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid #ccc;
      border-radius: 4px;
      cursor: pointer;
    }
    
    .sitecore-accordion-item-checkbox[data-state='checked'] {
      color: var(--sdc-palette-primary-contrastText);
      background-color: var(--sdc-palette-primary-main);
    }
    
    .sitecore-accordion-item-checkbox:focus {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* AccordionItemToggle */
    .sitecore-accordion-item-toggle {
      all: unset;
      width: 40px;
      height: 35px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: var(--sdc-typography-fontSize1-fontSize);
      margin-right: var(--sdc-spacing-s);
    }
    
    .sitecore-accordion-item-toggle:focus {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-accordion-item-toggle[data-state='on'] {
      background-color: var(--sdc-palette-primary-main);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    /* AccordionItemCheckboxIndicator */
    .sitecore-accordion-item-checkbox-indicator {
      color: var(--sdc-palette-primary-contrastText);
      width: 15px;
      height: 15px;
    }
    
    /* AccordionValueList */
    .sitecore-accordion-value-list {
      list-style: none;
    }
    
    .sitecore-accordion-value-list li {
      padding: var(--sdc-spacing-xs) 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    .sitecore-accordion-value-list[data-orientation='horizontal'] {
      display: flex;
      flex-direction: row;
    }
    
    /* AccordionItemCheckboxLabel */
    .sitecore-accordion-item-checkbox-label {
      padding-left: var(--sdc-spacing-xs);
      color: var(--sdc-palette-primary-main);
    }
    
    /* AccordionItem */
    .sitecore-accordion-item {
      display: flex;
      align-items: center;
    }
    
    /* AccordionHeader */
    .sitecore-accordion-header {
      display: flex;
      margin-top: var(--sdc-spacing-s);
      margin-bottom: var(--sdc-spacing-s);
    }
    
    /* AccordionTrigger */
    .sitecore-accordion-trigger {
      align-items: center;
      color: var(--sdc-palette-primary-main);
      display: flex;
      font-size: var(--sdc-typography-fontSize2-fontSize);
      height: 45px;
      justify-content: space-between;
      line-height: 1;
      padding: 0 var(--sdc-spacing-m);
      flex: 1 1 0;
      background: none;
      border: none;
    }
    
    /* AccordionFacetsFacet */
    .sitecore-accordion-facets-facet {
      border-bottom: solid 1px var(--sdc-palette-grey-800);
    }
    
    /* AccordionFacetsRoot */
    .sitecore-accordion-facets-root {
    }
    
    /* PaginationRoot */
    .sitecore-pagination-root {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      margin-top: var(--sdc-spacing-m);
    }
    
    /* PaginationPrevPage */
    .sitecore-pagination-prev-page {
      display: inline;
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-prev-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationNextPage */
    .sitecore-pagination-next-page {
      display: inline;
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-next-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationFirstPage */
    .sitecore-pagination-first-page {
      font-size: var(--sdc-typography-fontSize2-fontSize);
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-first-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationLastPage */
    .sitecore-pagination-last-page {
      font-size: var(--sdc-typography-fontSize2-fontSize);
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-last-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
    }
    
    /* PaginationPage */
    .sitecore-pagination-page {
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
    }
    
    /* PaginationPages */
    .sitecore-pagination-pages {
      display: inline;
    }
    
    /* Wrapper */
    .sitecore-wrapper {
    }
    
    /* MainArea */
    .sitecore-main-area {
      display: flex;
      max-width: 100%;
      position: relative;
    }
    
    /* LeftArea */
    .sitecore-left-area {
      position: relative;
      flex: 1 1;
      margin-right: var(--sdc-spacing-l);
    }
    
    /* RightArea */
    .sitecore-right-area {
      display: flex;
      flex-direction: column;
      flex: 4 1;
    }
    
    /* RightTopArea */
    .sitecore-right-top-area {
      display: flex;
      justify-content: space-between;
    }
    
    /* Grid */
    .sitecore-grid {
      width: 100%;
      display: grid;
      grid-gap: var(--sdc-spacing-m);
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      grid-auto-flow: row;
    }
    
    /* ClearFilters */
    .sitecore-clear-filters {
      background: none;
      border: none;
      color: var(--sdc-palette-primary-main);
    }
    
    .sitecore-clear-filters:hover {
      text-decoration: underline;
      cursor: pointer;
    }
    
    /* SelectedFiltersList */
    .sitecore-selected-filters-list {
      width: 100%;
      list-style: none;
      padding-left: var(--sdc-spacing-s);
    }
    
    /* SelectedFiltersListItem */
    .sitecore-selected-filters-list-item {
      display: flex;
      justify-content: space-between;
      padding: var(--sdc-spacing-xs) 0 var(--sdc-spacing-xs) 0;
    }
    
    /* SelectedFiltersListItemText */
    .sitecore-selected-filters-list-item-text {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* SelectedFiltersListItemButton */
    .sitecore-selected-filters-list-item-button {
      background: none;
      border: none;
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      cursor: pointer;
    }
    
    /* PageControls */
    .sitecore-page-controls {
      display: flex;
      justify-content: space-between;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* QuerySummary */
    .sitecore-query-summary {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: bold;
      margin: auto 0;
    }
    
    /* LoaderContainer */
    .sitecore-loader-container {
      align-items: center;
      display: flex;
      position: absolute;
      left: 0;
      right: 0;
      height: 100%;
      background-color: var(--sdc-palette-common-white);
      opacity: 0.5;
      z-index: 100;
    }
    
    /* LoaderAnimation */
    .sitecore-loader-animation {
      animation: rotate-animation 2s linear infinite;
      display: block;
      fill: var(--sdc-palette-primary-main);
      height: 50px;
      margin: auto;
      width: 50px;
    }
    
    /* NoResults */
    .sitecore-no-results {
      display: flex;
      justify-content: center;
      width: 100%;
    }

CSS module styled component

CSS module styled widget templates use UI primitives which are styled using a CSS module. To customize, you can edit the component and CSS module.

TS CLI

With the Sitecore Search CLI you can replicate all necessary files of the widget template in your React project. The following procedure includes instructions for installing the CLI in your project and specifying the location where widget components are created.

If you have previously installed the CLI in your project and have a location defined in a file called .sc-search-settings.json, you can proceed directly to step 3.

To install the UI Components CLI in your project:

  1. To install the Search UI Components CLI, open the terminal of your IDE or command prompt and run the following command:

    npx install --save-dev @sitecore-search/cli
  2. To define the location of where widget components will be created, at the root of your project, in a file called .sc-search-settings.json, specify the location as shown in the following code block.

    {
        "components-path": "src-test/components"
    }
  3. To create the widget component in index.tsx and styles.module.css files, go to the location you defined in the .sc-search-settings.json file then open the terminal of your IDE or command prompt and run the following command:

    npx sc-search new-widget --language typescript --template search-results-row-grid --entity content --styling cssModule

TS manual

When you manually create widget templates in your React project, you need to ensure that you install any required dependencies and create all necessary files.

To create the widget component using this widget template in your project:

  1. In your project, to install the development dependency, open the terminal of your IDE or command prompt and run the following command:

    npm install @radix-ui/react-icons@^1.3.0
  2. In a folder for the widget component, create files called index.tsx and styles.module.css then in respective files, paste following code blocks.

    // index.tsx
    import { useState } from 'react';
    
    import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, GridIcon, ListBulletIcon } from '@radix-ui/react-icons';
    import { Presence } from '@radix-ui/react-presence';
    import type { SearchResultsInitialState, SearchResultsStoreState } from '@sitecore-search/react';
    import { WidgetDataType, useSearchResults, useSearchResultsSelectedFilters, widget } from '@sitecore-search/react';
    import {
      AccordionFacets,
      ArticleCard,
      CardViewSwitcher,
      FacetItem,
      Pagination,
      SearchResultsAccordionFacets,
      Select,
      SortSelect,
    } from '@sitecore-search/ui';
    
    import styles from './styles.module.css';
    
    type ArticleModel = {
      id: string;
      type?: string;
      title?: string;
      name?: string;
      subtitle?: string;
      url?: string;
      description?: string;
      content_text?: string;
      image_url?: string;
      source_id?: string;
    };
    type ArticlesSearchResultsProps = {
      defaultSortType?: SearchResultsStoreState['sortType'];
      defaultPage?: SearchResultsStoreState['page'];
      defaultItemsPerPage?: SearchResultsStoreState['itemsPerPage'];
      defaultKeyphrase?: SearchResultsStoreState['keyphrase'];
    };
    type InitialState = SearchResultsInitialState<'itemsPerPage' | 'keyphrase' | 'page' | 'sortType'>;
    const buildRangeLabel = (min: number | undefined, max: number | undefined): string => {
      return typeof min === 'undefined' ? `< $${max}` : typeof max === 'undefined' ? ` > $${min}` : `$${min} - $${max}`;
    };
    const buildFacetLabel = (selectedFacet: any) => {
      if ('min' in selectedFacet || 'max' in selectedFacet) {
        return `${selectedFacet.facetLabel}: ${buildRangeLabel(selectedFacet.min, selectedFacet.max)}`;
      }
      return `${selectedFacet.facetLabel}: ${selectedFacet.valueLabel}`;
    };
    export const SearchResultsWithLayoutOptionComponent = ({
      defaultSortType = 'featured_desc',
      defaultPage = 1,
      defaultKeyphrase = '',
      defaultItemsPerPage = 24,
    }: ArticlesSearchResultsProps) => {
      const {
        widgetRef,
        actions: {
          onResultsPerPageChange,
          onPageNumberChange,
          onItemClick,
          onRemoveFilter,
          onSortChange,
          onFacetClick,
          onClearFilters,
        },
        state: { sortType, page, itemsPerPage },
        queryResult: {
          isLoading,
          isFetching,
          data: {
            total_item: totalItems = 0,
            sort: { choices: sortChoices = [] } = {},
            facet: facets = [],
            content: articles = [],
          } = {},
        },
      } = useSearchResults<ArticleModel, InitialState>({
        state: {
          sortType: defaultSortType,
          page: defaultPage,
          itemsPerPage: defaultItemsPerPage,
          keyphrase: defaultKeyphrase,
        },
      });
      const totalPages = Math.ceil(totalItems / itemsPerPage);
      const selectedSortIndex = sortChoices.findIndex((s) => s.name === sortType);
      const selectedFacetsFromApi = useSearchResultsSelectedFilters();
      const defaultCardView = CardViewSwitcher.CARD_VIEW_LIST;
      const [dir, setDir] = useState(defaultCardView);
      const onToggle = (value = defaultCardView) => setDir(value);
      return (
        <>
          {isLoading && (
            <div className={styles['sitecore-loader-container']}>
              <Presence present={isLoading}>
                <svg
                  aria-busy={isLoading}
                  aria-hidden={!isLoading}
                  focusable="false"
                  role="progressbar"
                  viewBox="0 0 20 20"
                  className={styles['sitecore-loader-animation']}
                >
                  <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                </svg>
              </Presence>
            </div>
          )}
          {!isLoading && (
            <div ref={widgetRef} className={styles['sitecore-wrapper']}>
              <div className={styles['sitecore-main-area']}>
                {isFetching && (
                  <div className={styles['sitecore-loader-container']}>
                    <Presence present={true}>
                      <svg
                        aria-busy={true}
                        aria-hidden={false}
                        focusable="false"
                        role="progressbar"
                        viewBox="0 0 20 20"
                        className={styles['sitecore-loader-animation']}
                      >
                        <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                      </svg>
                    </Presence>
                  </div>
                )}
                {totalItems > 0 && (
                  <>
                    <section className={styles['sitecore-left-area']}>
                      {selectedFacetsFromApi.length > 0 && (
                        <button onClick={onClearFilters} className={styles['sitecore-clear-filters']}>
                          Clear Filters
                        </button>
                      )}
                      <ul className={styles['sitecore-selected-filters-list']}>
                        {selectedFacetsFromApi.map((selectedFacet) => (
                          <li
                            key={`${selectedFacet.facetId}${selectedFacet.facetLabel}${selectedFacet.valueLabel}`}
                            className={styles['sitecore-selected-filters-list-item']}
                          >
                            <span className={styles['sitecore-selected-filters-list-item-text']}>
                              {buildFacetLabel(selectedFacet)}
                            </span>
                            <button
                              onClick={() => onRemoveFilter(selectedFacet)}
                              className={styles['sitecore-selected-filters-list-item-button']}
                            >
                              X
                            </button>
                          </li>
                        ))}
                      </ul>
                      <SearchResultsAccordionFacets
                        defaultFacetTypesExpandedList={[]}
                        onFacetTypesExpandedListChange={() => {}}
                        onFacetValueClick={onFacetClick}
                        className={styles['sitecore-accordion-facets-root']}
                      >
                        {facets.map((f) => (
                          <AccordionFacets.Facet
                            facetId={f.name}
                            key={f.name}
                            className={styles['sitecore-accordion-facets-facet']}
                          >
                            <AccordionFacets.Header className={styles['sitecore-accordion-header']}>
                              <AccordionFacets.Trigger className={styles['sitecore-accordion-trigger']}>
                                {f.label}
                              </AccordionFacets.Trigger>
                            </AccordionFacets.Header>
                            <AccordionFacets.Content>
                              <AccordionFacets.ValueList className={styles['sitecore-accordion-value-list']}>
                                {f.value.map((v, index) => (
                                  <FacetItem
                                    {...{
                                      index,
                                      facetValueId: v.id,
                                    }}
                                    key={v.id}
                                    className={styles['sitecore-accordion-item']}
                                  >
                                    <AccordionFacets.ItemCheckbox className={styles['sitecore-accordion-item-checkbox']}>
                                      <AccordionFacets.ItemCheckboxIndicator
                                        className={styles['sitecore-accordion-item-checkbox-indicator']}
                                      >
                                        <CheckIcon />
                                      </AccordionFacets.ItemCheckboxIndicator>
                                    </AccordionFacets.ItemCheckbox>
                                    <AccordionFacets.ItemLabel className={styles['sitecore-accordion-item-checkbox-label']}>
                                      {v.text} {v.count && `(${v.count})`}
                                    </AccordionFacets.ItemLabel>
                                  </FacetItem>
                                ))}
                              </AccordionFacets.ValueList>
                            </AccordionFacets.Content>
                          </AccordionFacets.Facet>
                        ))}
                      </SearchResultsAccordionFacets>
                    </section>
                    <section className={styles['sitecore-right-area']}>
                      <section className={styles['sitecore-right-top-area']}>
                        {totalItems && (
                          <div className={styles['sitecore-query-summary']}>
                            <b>
                              Showing {itemsPerPage * (page - 1) + 1} - {itemsPerPage * (page - 1) + articles.length} of{' '}
                              {totalItems} results
                            </b>
                          </div>
                        )}
    
                        <div>
                          {/* Card View Switcher */}
                          <CardViewSwitcher.Root
                            onValueChange={onToggle}
                            defaultValue={defaultCardView}
                            className={styles['sitecore-card-view-switcher-root']}
                          >
                            <CardViewSwitcher.Item
                              value="grid"
                              aria-label="Grid View"
                              className={styles['sitecore-card-view-switcher-item']}
                            >
                              <GridIcon />
                            </CardViewSwitcher.Item>
                            <CardViewSwitcher.Item
                              value="list"
                              aria-label="List View"
                              className={styles['sitecore-card-view-switcher-item']}
                            >
                              <ListBulletIcon />
                            </CardViewSwitcher.Item>
                          </CardViewSwitcher.Root>
    
                          {/* Sort Select */}
                          <SortSelect.Root defaultValue={sortChoices[selectedSortIndex]?.name} onValueChange={onSortChange}>
                            <SortSelect.Trigger className={styles['sitecore-sort-select-trigger']}>
                              <SortSelect.SelectValue className={styles['sitecore-sort-select-value']}>
                                {selectedSortIndex > -1 ? sortChoices[selectedSortIndex].label : ''}
                              </SortSelect.SelectValue>
                              <SortSelect.Icon className={styles['sitecore-sort-select-icon']} />
                            </SortSelect.Trigger>
                            <SortSelect.Content className={styles['sitecore-sort-select-content']}>
                              <SortSelect.Viewport className={styles['sitecore-sort-select-viewport']}>
                                {sortChoices.map((option) => (
                                  <SortSelect.Option
                                    value={option}
                                    key={option.name}
                                    className={styles['sitecore-sort-select-option']}
                                  >
                                    <SortSelect.OptionText className={styles['sitecore-sort-select-option-text']}>
                                      {option.label}
                                    </SortSelect.OptionText>
                                  </SortSelect.Option>
                                ))}
                              </SortSelect.Viewport>
                            </SortSelect.Content>
                          </SortSelect.Root>
                        </div>
                      </section>
    
                      {/* Results */}
                      {dir === CardViewSwitcher.CARD_VIEW_GRID ? (
                        <div className={styles['sitecore-grid']}>
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id} className={styles['sitecore-article-root']}>
                              <div className={styles['sitecore-image-wrapper']}>
                                <ArticleCard.Image src={a.image_url} className={styles['sitecore-article-image']} />
                              </div>
                              <ArticleCard.Id className={styles['sitecore-article-id']}>{a.id}</ArticleCard.Id>
                              <ArticleCard.Title className={styles['sitecore-article-title']}>
                                <a
                                  href="#"
                                  onClick={(e) => {
                                    e.preventDefault();
                                    onItemClick({
                                      id: a.id,
                                      index,
                                      sourceId: a.source_id,
                                    });
                                  }}
                                  className={styles['sitecore-article-link']}
                                >
                                  {a.name}
                                </a>
                              </ArticleCard.Title>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      ) : (
                        <div className={styles['sitecore-row']}>
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id} className={styles['sitecore-article-card-row-root']}>
                              <div className={styles['sitecore-article-card-row-left']}>
                                <ArticleCard.Image
                                  src={a.image_url}
                                  className={styles['sitecore-article-card-row-image']}
                                />
                              </div>
                              <div className={styles['sitecore-article-card-row-right']}>
                                <ArticleCard.Title className={styles['sitecore-article-card-row-title']}>
                                  <a
                                    href="#"
                                    onClick={(e) => {
                                      e.preventDefault();
                                      onItemClick({
                                        id: a.id,
                                        index,
                                        sourceId: a.source_id,
                                      });
                                    }}
                                    className={styles['sitecore-article-card-row-link']}
                                  >
                                    {a.name}
                                  </a>
                                </ArticleCard.Title>
                                <ArticleCard.Content className={styles['sitecore-article-card-row-content']}>
                                  {a.content_text}
                                </ArticleCard.Content>
                              </div>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      )}
                      <div className={styles['sitecore-page-controls']}>
                        <div>
                          <label>Results Per Page</label>
                          <Select.Root
                            defaultValue={String(defaultItemsPerPage)}
                            onValueChange={(v) =>
                              onResultsPerPageChange({
                                numItems: Number(v),
                              })
                            }
                          >
                            <Select.Trigger className={styles['sitecore-generic-select-trigger']}>
                              <Select.SelectValue className={styles['sitecore-generic-select-value']} />
                              <Select.Icon className={styles['sitecore-generic-select-icon']} />
                            </Select.Trigger>
                            <Select.SelectContent className={styles['sitecore-generic-select-content']}>
                              <Select.Viewport className={styles['sitecore-generic-select-viewport']}>
                                <Select.SelectItem value="24" className={styles['sitecore-generic-select-option']}>
                                  <SortSelect.OptionText className={styles['sitecore-generic-select-option-text']}>
                                    24
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="48" className={styles['sitecore-generic-select-option']}>
                                  <SortSelect.OptionText className={styles['sitecore-generic-select-option-text']}>
                                    48
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="64" className={styles['sitecore-generic-select-option']}>
                                  <SortSelect.OptionText className={styles['sitecore-generic-select-option-text']}>
                                    64
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
                              </Select.Viewport>
                            </Select.SelectContent>
                          </Select.Root>
                        </div>
                        <Pagination.Root
                          currentPage={page}
                          defaultCurrentPage={1}
                          totalPages={totalPages}
                          onPageChange={(v) =>
                            onPageNumberChange({
                              page: v,
                            })
                          }
                          className={styles['sitecore-pagination-root']}
                        >
                          <Pagination.PrevPage
                            onClick={(e) => e.preventDefault()}
                            className={styles['sitecore-pagination-prev-page']}
                          >
                            <ArrowLeftIcon />
                          </Pagination.PrevPage>
                          <Pagination.Pages className={styles['sitecore-pagination-pages']}>
                            {(pagination) =>
                              Pagination.paginationLayout(pagination, {}).map(({ page, type }) =>
                                type === 'page' ? (
                                  <Pagination.Page
                                    key={page}
                                    aria-label={`Page ${page}`}
                                    page={page as number}
                                    onClick={(e) => e.preventDefault()}
                                    className={styles['sitecore-pagination-page']}
                                  >
                                    {page}
                                  </Pagination.Page>
                                ) : (
                                  <span key={type}>...</span>
                                ),
                              )
                            }
                          </Pagination.Pages>
                          <Pagination.NextPage
                            onClick={(e) => e.preventDefault()}
                            className={styles['sitecore-pagination-next-page']}
                          >
                            <ArrowRightIcon />
                          </Pagination.NextPage>
                        </Pagination.Root>
                      </div>
                    </section>
                  </>
                )}
                {totalItems <= 0 && !isFetching && (
                  <div className={styles['sitecore-no-results']}>
                    <h3>0 Results</h3>
                  </div>
                )}
              </div>
            </div>
          )}
        </>
      );
    };
    const SearchResultsWithLayoutOptionWidget = widget(SearchResultsWithLayoutOptionComponent, WidgetDataType.SEARCH_RESULTS, 'content');
    export default SearchResultsWithLayoutOptionWidget;
    // styles.module.css
    @keyframes rotate-animation {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    
    /* ArticleCardRowRoot */
    .sitecore-article-card-row-root {
      box-sizing: border-box;
      border: solid 1px var(--sdc-palette-grey-400);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      display: flex;
      height: 200px;
      width: 100%;
      padding: var(--sdc-spacing-m);
      margin-bottom: var(--sdc-spacing-m);
      cursor: pointer;
    }
    
    .sitecore-article-card-row-root:focus-within {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-card-row-root:hover {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* ArticleCardRowLeft */
    .sitecore-article-card-row-left {
      position: relative;
      height: 164px;
      width: 164px;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleCardRowRight */
    .sitecore-article-card-row-right {
      box-sizing: border-box;
      padding-left: var(--sdc-spacing-m);
      overflow: hidden;
      max-width: 80%;
    }
    
    /* ArticleCardRowImage */
    .sitecore-article-card-row-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ArticleCardRowTitle */
    .sitecore-article-card-row-title {
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize4-fontSize);
      font-weight: var(--sdc-typography-fontSize4-fontWeight);
      line-height: var(--sdc-typography-fontSize4-lineHeight);
    }
    
    /* ArticleCardRowContent */
    .sitecore-article-card-row-content {
      margin: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: var(--sdc-typography-fontWeight);
      line-height: var(--sdc-typography-lineHeight);
      color: var(--sdc-palette-primary-main);
    }
    
    /* ArticleCardRowId */
    .sitecore-article-card-row-id {
      font-family: var(--sdc-typography-fontFamilySystem);
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* ArticleCardRowLink */
    .sitecore-article-card-row-link {
      text-decoration: none;
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize4-fontSize);
    }
    
    .sitecore-article-card-row-link:hover {
      text-decoration: none;
    }
    
    .sitecore-article-card-row-link:focus {
      text-decoration: none;
    }
    
    /* CardViewSwitcherRoot */
    .sitecore-card-view-switcher-root {
      display: inline-flex;
      background-color: var(--sdc-palette-common-white);
    }
    
    /* CardViewSwitcherItem */
    .sitecore-card-view-switcher-item {
      all: unset;
      align-items: center;
      background-color: var(--sdc-palette-common-white);
      color: var(--sdc-palette-grey-500);
      display: flex;
      height: 30px;
      justify-content: center;
      margin-left: var(--sdc-spacing-s);
      width: 30px;
    }
    
    .sitecore-card-view-switcher-item:first-child {
      margin-left: 0;
    }
    
    .sitecore-card-view-switcher-item:hover {
      background-color: var(--sdc-palette-primary-light);
    }
    
    .sitecore-card-view-switcher-item[data-state='on'] {
      background-color: var(--sdc-palette-primary-main);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-card-view-switcher-item:focus {
      position: relative;
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* Row */
    .sitecore-row {
      width: 100%;
      display: block;
    }
    
    /* BreadcrumbRoot */
    .sitecore-breadcrumb-root {
      width: 100%;
      display: inline-block;
    }
    
    /* BreadcrumbList */
    .sitecore-breadcrumb-list {
      list-style: none;
      padding: 0;
    }
    
    /* BreadcrumbItem */
    .sitecore-breadcrumb-item {
      display: inline;
      color: var(--sdc-palette-primary-main);
      margin-left: var(--sdc-spacing-xs);
    }
    
    /* BreadcrumbNavigation */
    .sitecore-breadcrumb-navigation {
    }
    
    /* BreadcrumbLink */
    .sitecore-breadcrumb-link {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      text-decoration: none;
    }
    
    /* BreadcrumbSeparator */
    .sitecore-breadcrumb-separator {
    }
    
    /* SortSelectTrigger */
    .sitecore-sort-select-trigger {
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: var(--sdc-spacing-xs);
      background-color: transparent;
      height: 40px;
      padding: var(--sdc-spacing-xs) var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      border: none;
    }
    
    .sitecore-sort-select-trigger:focus {
      outline: none;
    }
    
    /* GenericSelectTrigger */
    .sitecore-generic-select-trigger {
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: var(--sdc-spacing-xs);
      background-color: transparent;
      height: 40px;
      padding: var(--sdc-spacing-xs) var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      border: none;
    }
    
    .sitecore-generic-select-trigger:focus {
      outline: none;
    }
    
    /* SortSelectContent */
    .sitecore-sort-select-content {
      background-color: var(--sdc-palette-primary-contrastText);
      overflow: hidden;
      color: var(--sdc-palette-primary-main);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      position: absolute;
      top: 30px;
      z-index: 100;
    }
    
    .sitecore-sort-select-content:focus-within {
      border-color: var(--sdc-palette-primary-dark);
    }
    
    /* GenericSelectContent */
    .sitecore-generic-select-content {
      background-color: var(--sdc-palette-primary-contrastText);
      overflow: hidden;
      color: var(--sdc-palette-primary-main);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      position: absolute;
      top: 30px;
      z-index: 100;
    }
    
    .sitecore-generic-select-content:focus-within {
      border-color: var(--sdc-palette-primary-dark);
    }
    
    /* SortSelectViewport */
    .sitecore-sort-select-viewport {
      padding: var(--sdc-spacing-xs);
      z-index: 50000;
    }
    
    /* GenericSelectViewport */
    .sitecore-generic-select-viewport {
      padding: var(--sdc-spacing-xs);
      z-index: 50000;
    }
    
    /* SortSelectOption */
    .sitecore-sort-select-option {
      display: flex;
      align-items: center;
      line-height: 1;
      cursor: pointer;
      -webkit-user-select: none;
      -moz-user-select: none;
      user-select: none;
      white-space: nowrap;
      height: 25px;
      padding: 0 var(--sdc-spacing-xs);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      position: relative;
      border-radius: 0;
    }
    
    .sitecore-sort-select-option[data-highlighted] {
      border-radius: 0;
      background-color: var(--sdc-palette-primary-dark);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-sort-select-option[data-disabled] {
      color: var(--sdc-palette-grey-800);
      font-style: italic;
    }
    
    /* GenericSelectOption */
    .sitecore-generic-select-option {
      display: flex;
      align-items: center;
      line-height: 1;
      cursor: pointer;
      -webkit-user-select: none;
      -moz-user-select: none;
      user-select: none;
      white-space: nowrap;
      height: 25px;
      padding: 0 var(--sdc-spacing-xs);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      position: relative;
      border-radius: 0;
    }
    
    .sitecore-generic-select-option[data-highlighted] {
      border-radius: 0;
      background-color: var(--sdc-palette-primary-dark);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-generic-select-option[data-disabled] {
      color: var(--sdc-palette-grey-800);
      font-style: italic;
    }
    
    /* SortSelectValue */
    .sitecore-sort-select-value {
      color: var(--sdc-palette-primary-main);
    }
    
    /* GenericSelectValue */
    .sitecore-generic-select-value {
      color: var(--sdc-palette-primary-main);
    }
    
    /* SortSelectIcon */
    .sitecore-sort-select-icon {
    }
    
    /* GenericSelectIcon */
    .sitecore-generic-select-icon {
    }
    
    /* SortSelectOptionText */
    .sitecore-sort-select-option-text {
    }
    
    /* GenericSelectOptionText */
    .sitecore-generic-select-option-text {
    }
    
    /* ArticleRoot */
    .sitecore-article-root {
      border: solid 1px var(--sdc-palette-grey-400);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      width: 180px;
      padding: var(--sdc-spacing-m);
      cursor: pointer;
      display: block;
    }
    
    .sitecore-article-root:focus-within {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-root:hover {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* ArticleImage */
    .sitecore-article-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ImageWrapper */
    .sitecore-image-wrapper {
      margin: auto auto 10px;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleTitle */
    .sitecore-article-title {
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize3-fontSize);
      font-weight: var(--sdc-typography-fontSize3-fontWeight);
      line-height: var(--sdc-typography-fontSize3-lineHeight);
    }
    
    /* ArticleContent */
    .sitecore-article-content {
      margin: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: var(--sdc-typography-fontWeight);
      line-height: var(--sdc-typography-lineHeight);
      color: var(--sdc-palette-primary-main);
    }
    
    /* ArticleId */
    .sitecore-article-id {
      font-family: var(--sdc-typography-fontFamilySystem);
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* ArticleLink */
    .sitecore-article-link {
      text-decoration: none;
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize4-fontSize);
    }
    
    .sitecore-article-link:hover {
      text-decoration: none;
    }
    
    .sitecore-article-link:focus {
      text-decoration: none;
    }
    
    /* AccordionItemCheckbox */
    .sitecore-accordion-item-checkbox {
      all: unset;
      background-color: white;
      width: 20px;
      height: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid #ccc;
      border-radius: 4px;
      cursor: pointer;
    }
    
    .sitecore-accordion-item-checkbox[data-state='checked'] {
      color: var(--sdc-palette-primary-contrastText);
      background-color: var(--sdc-palette-primary-main);
    }
    
    .sitecore-accordion-item-checkbox:focus {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* AccordionItemToggle */
    .sitecore-accordion-item-toggle {
      all: unset;
      width: 40px;
      height: 35px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: var(--sdc-typography-fontSize1-fontSize);
      margin-right: var(--sdc-spacing-s);
    }
    
    .sitecore-accordion-item-toggle:focus {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-accordion-item-toggle[data-state='on'] {
      background-color: var(--sdc-palette-primary-main);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    /* AccordionItemCheckboxIndicator */
    .sitecore-accordion-item-checkbox-indicator {
      color: var(--sdc-palette-primary-contrastText);
      width: 15px;
      height: 15px;
    }
    
    /* AccordionValueList */
    .sitecore-accordion-value-list {
      list-style: none;
    }
    
    .sitecore-accordion-value-list li {
      padding: var(--sdc-spacing-xs) 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    .sitecore-accordion-value-list[data-orientation='horizontal'] {
      display: flex;
      flex-direction: row;
    }
    
    /* AccordionItemCheckboxLabel */
    .sitecore-accordion-item-checkbox-label {
      padding-left: var(--sdc-spacing-xs);
      color: var(--sdc-palette-primary-main);
    }
    
    /* AccordionItem */
    .sitecore-accordion-item {
      display: flex;
      align-items: center;
    }
    
    /* AccordionHeader */
    .sitecore-accordion-header {
      display: flex;
      margin-top: var(--sdc-spacing-s);
      margin-bottom: var(--sdc-spacing-s);
    }
    
    /* AccordionTrigger */
    .sitecore-accordion-trigger {
      align-items: center;
      color: var(--sdc-palette-primary-main);
      display: flex;
      font-size: var(--sdc-typography-fontSize2-fontSize);
      height: 45px;
      justify-content: space-between;
      line-height: 1;
      padding: 0 var(--sdc-spacing-m);
      flex: 1 1 0;
      background: none;
      border: none;
    }
    
    /* AccordionFacetsFacet */
    .sitecore-accordion-facets-facet {
      border-bottom: solid 1px var(--sdc-palette-grey-800);
    }
    
    /* AccordionFacetsRoot */
    .sitecore-accordion-facets-root {
    }
    
    /* PaginationRoot */
    .sitecore-pagination-root {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      margin-top: var(--sdc-spacing-m);
    }
    
    /* PaginationPrevPage */
    .sitecore-pagination-prev-page {
      display: inline;
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-prev-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationNextPage */
    .sitecore-pagination-next-page {
      display: inline;
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-next-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationFirstPage */
    .sitecore-pagination-first-page {
      font-size: var(--sdc-typography-fontSize2-fontSize);
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-first-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationLastPage */
    .sitecore-pagination-last-page {
      font-size: var(--sdc-typography-fontSize2-fontSize);
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-last-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
    }
    
    /* PaginationPage */
    .sitecore-pagination-page {
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
    }
    
    /* PaginationPages */
    .sitecore-pagination-pages {
      display: inline;
    }
    
    /* Wrapper */
    .sitecore-wrapper {
    }
    
    /* MainArea */
    .sitecore-main-area {
      display: flex;
      max-width: 100%;
      position: relative;
    }
    
    /* LeftArea */
    .sitecore-left-area {
      position: relative;
      flex: 1 1;
      margin-right: var(--sdc-spacing-l);
    }
    
    /* RightArea */
    .sitecore-right-area {
      display: flex;
      flex-direction: column;
      flex: 4 1;
    }
    
    /* RightTopArea */
    .sitecore-right-top-area {
      display: flex;
      justify-content: space-between;
    }
    
    /* Grid */
    .sitecore-grid {
      width: 100%;
      display: grid;
      grid-gap: var(--sdc-spacing-m);
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      grid-auto-flow: row;
    }
    
    /* ClearFilters */
    .sitecore-clear-filters {
      background: none;
      border: none;
      color: var(--sdc-palette-primary-main);
    }
    
    .sitecore-clear-filters:hover {
      text-decoration: underline;
      cursor: pointer;
    }
    
    /* SelectedFiltersList */
    .sitecore-selected-filters-list {
      width: 100%;
      list-style: none;
      padding-left: var(--sdc-spacing-s);
    }
    
    /* SelectedFiltersListItem */
    .sitecore-selected-filters-list-item {
      display: flex;
      justify-content: space-between;
      padding: var(--sdc-spacing-xs) 0 var(--sdc-spacing-xs) 0;
    }
    
    /* SelectedFiltersListItemText */
    .sitecore-selected-filters-list-item-text {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* SelectedFiltersListItemButton */
    .sitecore-selected-filters-list-item-button {
      background: none;
      border: none;
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      cursor: pointer;
    }
    
    /* PageControls */
    .sitecore-page-controls {
      display: flex;
      justify-content: space-between;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* QuerySummary */
    .sitecore-query-summary {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: bold;
      margin: auto 0;
    }
    
    /* LoaderContainer */
    .sitecore-loader-container {
      align-items: center;
      display: flex;
      position: absolute;
      left: 0;
      right: 0;
      height: 100%;
      background-color: var(--sdc-palette-common-white);
      opacity: 0.5;
      z-index: 100;
    }
    
    /* LoaderAnimation */
    .sitecore-loader-animation {
      animation: rotate-animation 2s linear infinite;
      display: block;
      fill: var(--sdc-palette-primary-main);
      height: 50px;
      margin: auto;
      width: 50px;
    }
    
    /* NoResults */
    .sitecore-no-results {
      display: flex;
      justify-content: center;
      width: 100%;
    }

JS CLI

With the Sitecore Search CLI you can replicate all necessary files of the widget template in your React project. The following procedure includes instructions for installing the CLI in your project and specifying the location where widget components are created.

If you have previously installed the CLI in your project and have a location defined in a file called .sc-search-settings.json, you can proceed directly to step 3.

To install the UI Components CLI in your project:

  1. To install the Search UI Components CLI, open the terminal of your IDE or command prompt and run the following command:

    npx install --save-dev @sitecore-search/cli
  2. To define the location of where widget components will be created, at the root of your project, in a file called .sc-search-settings.json, specify the location as shown in the following code block.

    {
        "components-path": "src-test/components"
    }
  3. To create the widget component in index.jsx and styles.module.css files, go to the location you defined in the .sc-search-settings.json file then open the terminal of your IDE or command prompt and run the following command:

    npx sc-search new-widget --language javascript --template search-results-row-grid --entity content --styling cssModule

JS manual

When you manually create widget templates in your React project, you need to ensure that you install any required dependencies and create all necessary files.

To create the widget component using this widget template in your project:

  1. In your project, to install the development dependency, open the terminal of your IDE or command prompt and run the following command:

    npm install @radix-ui/react-icons@^1.3.0
  2. In a folder for the widget component, create files called index.jsx and styles.module.css then in respective files, paste following code blocks.

    // index.jsx
    import { useState } from 'react';
    
    import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, GridIcon, ListBulletIcon } from '@radix-ui/react-icons';
    import { Presence } from '@radix-ui/react-presence';
    import { WidgetDataType, useSearchResults, useSearchResultsSelectedFilters, widget } from '@sitecore-search/react';
    import {
      AccordionFacets,
      ArticleCard,
      CardViewSwitcher,
      FacetItem,
      Pagination,
      SearchResultsAccordionFacets,
      Select,
      SortSelect,
    } from '@sitecore-search/ui';
    
    import styles from './styles.module.css';
    
    const buildRangeLabel = (min, max) => {
      return typeof min === 'undefined' ? `< $${max}` : typeof max === 'undefined' ? ` > $${min}` : `$${min} - $${max}`;
    };
    const buildFacetLabel = (selectedFacet) => {
      if ('min' in selectedFacet || 'max' in selectedFacet) {
        return `${selectedFacet.facetLabel}: ${buildRangeLabel(selectedFacet.min, selectedFacet.max)}`;
      }
      return `${selectedFacet.facetLabel}: ${selectedFacet.valueLabel}`;
    };
    export const SearchResultsWithLayoutOptionComponent = ({
      defaultSortType = 'featured_desc',
      defaultPage = 1,
      defaultKeyphrase = '',
      defaultItemsPerPage = 24,
    }) => {
      const {
        widgetRef,
        actions: {
          onResultsPerPageChange,
          onPageNumberChange,
          onItemClick,
          onRemoveFilter,
          onSortChange,
          onFacetClick,
          onClearFilters,
        },
        state: { sortType, page, itemsPerPage },
        queryResult: {
          isLoading,
          isFetching,
          data: {
            total_item: totalItems = 0,
            sort: { choices: sortChoices = [] } = {},
            facet: facets = [],
            content: articles = [],
          } = {},
        },
      } = useSearchResults({
        state: {
          sortType: defaultSortType,
          page: defaultPage,
          itemsPerPage: defaultItemsPerPage,
          keyphrase: defaultKeyphrase,
        },
      });
      const totalPages = Math.ceil(totalItems / itemsPerPage);
      const selectedSortIndex = sortChoices.findIndex((s) => s.name === sortType);
      const selectedFacetsFromApi = useSearchResultsSelectedFilters();
      const defaultCardView = CardViewSwitcher.CARD_VIEW_LIST;
      const [dir, setDir] = useState(defaultCardView);
      const onToggle = (value = defaultCardView) => setDir(value);
      return (
        <>
          {isLoading && (
            <div className={styles['sitecore-loader-container']}>
              <Presence present={isLoading}>
                <svg
                  aria-busy={isLoading}
                  aria-hidden={!isLoading}
                  focusable="false"
                  role="progressbar"
                  viewBox="0 0 20 20"
                  className={styles['sitecore-loader-animation']}
                >
                  <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                </svg>
              </Presence>
            </div>
          )}
          {!isLoading && (
            <div ref={widgetRef} className={styles['sitecore-wrapper']}>
              <div className={styles['sitecore-main-area']}>
                {isFetching && (
                  <div className={styles['sitecore-loader-container']}>
                    <Presence present={true}>
                      <svg
                        aria-busy={true}
                        aria-hidden={false}
                        focusable="false"
                        role="progressbar"
                        viewBox="0 0 20 20"
                        className={styles['sitecore-loader-animation']}
                      >
                        <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
                      </svg>
                    </Presence>
                  </div>
                )}
                {totalItems > 0 && (
                  <>
                    <section className={styles['sitecore-left-area']}>
                      {selectedFacetsFromApi.length > 0 && (
                        <button onClick={onClearFilters} className={styles['sitecore-clear-filters']}>
                          Clear Filters
                        </button>
                      )}
                      <ul className={styles['sitecore-selected-filters-list']}>
                        {selectedFacetsFromApi.map((selectedFacet) => (
                          <li
                            key={`${selectedFacet.facetId}${selectedFacet.facetLabel}${selectedFacet.valueLabel}`}
                            className={styles['sitecore-selected-filters-list-item']}
                          >
                            <span className={styles['sitecore-selected-filters-list-item-text']}>
                              {buildFacetLabel(selectedFacet)}
                            </span>
                            <button
                              onClick={() => onRemoveFilter(selectedFacet)}
                              className={styles['sitecore-selected-filters-list-item-button']}
                            >
                              X
                            </button>
                          </li>
                        ))}
                      </ul>
                      <SearchResultsAccordionFacets
                        defaultFacetTypesExpandedList={[]}
                        onFacetTypesExpandedListChange={() => {}}
                        onFacetValueClick={onFacetClick}
                        className={styles['sitecore-accordion-facets-root']}
                      >
                        {facets.map((f) => (
                          <AccordionFacets.Facet
                            facetId={f.name}
                            key={f.name}
                            className={styles['sitecore-accordion-facets-facet']}
                          >
                            <AccordionFacets.Header className={styles['sitecore-accordion-header']}>
                              <AccordionFacets.Trigger className={styles['sitecore-accordion-trigger']}>
                                {f.label}
                              </AccordionFacets.Trigger>
                            </AccordionFacets.Header>
                            <AccordionFacets.Content>
                              <AccordionFacets.ValueList className={styles['sitecore-accordion-value-list']}>
                                {f.value.map((v, index) => (
                                  <FacetItem
                                    {...{
                                      index,
                                      facetValueId: v.id,
                                    }}
                                    key={v.id}
                                    className={styles['sitecore-accordion-item']}
                                  >
                                    <AccordionFacets.ItemCheckbox className={styles['sitecore-accordion-item-checkbox']}>
                                      <AccordionFacets.ItemCheckboxIndicator
                                        className={styles['sitecore-accordion-item-checkbox-indicator']}
                                      >
                                        <CheckIcon />
                                      </AccordionFacets.ItemCheckboxIndicator>
                                    </AccordionFacets.ItemCheckbox>
                                    <AccordionFacets.ItemLabel className={styles['sitecore-accordion-item-checkbox-label']}>
                                      {v.text} {v.count && `(${v.count})`}
                                    </AccordionFacets.ItemLabel>
                                  </FacetItem>
                                ))}
                              </AccordionFacets.ValueList>
                            </AccordionFacets.Content>
                          </AccordionFacets.Facet>
                        ))}
                      </SearchResultsAccordionFacets>
                    </section>
                    <section className={styles['sitecore-right-area']}>
                      <section className={styles['sitecore-right-top-area']}>
                        {totalItems && (
                          <div className={styles['sitecore-query-summary']}>
                            <b>
                              Showing {itemsPerPage * (page - 1) + 1} - {itemsPerPage * (page - 1) + articles.length} of{' '}
                              {totalItems} results
                            </b>
                          </div>
                        )}
    
                        <div>
                          {/* Card View Switcher */}
                          <CardViewSwitcher.Root
                            onValueChange={onToggle}
                            defaultValue={defaultCardView}
                            className={styles['sitecore-card-view-switcher-root']}
                          >
                            <CardViewSwitcher.Item
                              value="grid"
                              aria-label="Grid View"
                              className={styles['sitecore-card-view-switcher-item']}
                            >
                              <GridIcon />
                            </CardViewSwitcher.Item>
                            <CardViewSwitcher.Item
                              value="list"
                              aria-label="List View"
                              className={styles['sitecore-card-view-switcher-item']}
                            >
                              <ListBulletIcon />
                            </CardViewSwitcher.Item>
                          </CardViewSwitcher.Root>
    
                          {/* Sort Select */}
                          <SortSelect.Root defaultValue={sortChoices[selectedSortIndex]?.name} onValueChange={onSortChange}>
                            <SortSelect.Trigger className={styles['sitecore-sort-select-trigger']}>
                              <SortSelect.SelectValue className={styles['sitecore-sort-select-value']}>
                                {selectedSortIndex > -1 ? sortChoices[selectedSortIndex].label : ''}
                              </SortSelect.SelectValue>
                              <SortSelect.Icon className={styles['sitecore-sort-select-icon']} />
                            </SortSelect.Trigger>
                            <SortSelect.Content className={styles['sitecore-sort-select-content']}>
                              <SortSelect.Viewport className={styles['sitecore-sort-select-viewport']}>
                                {sortChoices.map((option) => (
                                  <SortSelect.Option
                                    value={option}
                                    key={option.name}
                                    className={styles['sitecore-sort-select-option']}
                                  >
                                    <SortSelect.OptionText className={styles['sitecore-sort-select-option-text']}>
                                      {option.label}
                                    </SortSelect.OptionText>
                                  </SortSelect.Option>
                                ))}
                              </SortSelect.Viewport>
                            </SortSelect.Content>
                          </SortSelect.Root>
                        </div>
                      </section>
    
                      {/* Results */}
                      {dir === CardViewSwitcher.CARD_VIEW_GRID ? (
                        <div className={styles['sitecore-grid']}>
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id} className={styles['sitecore-article-root']}>
                              <div className={styles['sitecore-image-wrapper']}>
                                <ArticleCard.Image src={a.image_url} className={styles['sitecore-article-image']} />
                              </div>
                              <ArticleCard.Id className={styles['sitecore-article-id']}>{a.id}</ArticleCard.Id>
                              <ArticleCard.Title className={styles['sitecore-article-title']}>
                                <a
                                  href="#"
                                  onClick={(e) => {
                                    e.preventDefault();
                                    onItemClick({
                                      id: a.id,
                                      index,
                                      sourceId: a.source_id,
                                    });
                                  }}
                                  className={styles['sitecore-article-link']}
                                >
                                  {a.name}
                                </a>
                              </ArticleCard.Title>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      ) : (
                        <div className={styles['sitecore-row']}>
                          {articles.map((a, index) => (
                            <ArticleCard.Root key={a.id} className={styles['sitecore-article-card-row-root']}>
                              <div className={styles['sitecore-article-card-row-left']}>
                                <ArticleCard.Image
                                  src={a.image_url}
                                  className={styles['sitecore-article-card-row-image']}
                                />
                              </div>
                              <div className={styles['sitecore-article-card-row-right']}>
                                <ArticleCard.Title className={styles['sitecore-article-card-row-title']}>
                                  <a
                                    href="#"
                                    onClick={(e) => {
                                      e.preventDefault();
                                      onItemClick({
                                        id: a.id,
                                        index,
                                        sourceId: a.source_id,
                                      });
                                    }}
                                    className={styles['sitecore-article-card-row-link']}
                                  >
                                    {a.name}
                                  </a>
                                </ArticleCard.Title>
                                <ArticleCard.Content className={styles['sitecore-article-card-row-content']}>
                                  {a.content_text}
                                </ArticleCard.Content>
                              </div>
                            </ArticleCard.Root>
                          ))}
                        </div>
                      )}
                      <div className={styles['sitecore-page-controls']}>
                        <div>
                          <label>Results Per Page</label>
                          <Select.Root
                            defaultValue={String(defaultItemsPerPage)}
                            onValueChange={(v) =>
                              onResultsPerPageChange({
                                numItems: Number(v),
                              })
                            }
                          >
                            <Select.Trigger className={styles['sitecore-generic-select-trigger']}>
                              <Select.SelectValue className={styles['sitecore-generic-select-value']} />
                              <Select.Icon className={styles['sitecore-generic-select-icon']} />
                            </Select.Trigger>
                            <Select.SelectContent className={styles['sitecore-generic-select-content']}>
                              <Select.Viewport className={styles['sitecore-generic-select-viewport']}>
                                <Select.SelectItem value="24" className={styles['sitecore-generic-select-option']}>
                                  <SortSelect.OptionText className={styles['sitecore-generic-select-option-text']}>
                                    24
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="48" className={styles['sitecore-generic-select-option']}>
                                  <SortSelect.OptionText className={styles['sitecore-generic-select-option-text']}>
                                    48
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
    
                                <Select.SelectItem value="64" className={styles['sitecore-generic-select-option']}>
                                  <SortSelect.OptionText className={styles['sitecore-generic-select-option-text']}>
                                    64
                                  </SortSelect.OptionText>
                                </Select.SelectItem>
                              </Select.Viewport>
                            </Select.SelectContent>
                          </Select.Root>
                        </div>
                        <Pagination.Root
                          currentPage={page}
                          defaultCurrentPage={1}
                          totalPages={totalPages}
                          onPageChange={(v) =>
                            onPageNumberChange({
                              page: v,
                            })
                          }
                          className={styles['sitecore-pagination-root']}
                        >
                          <Pagination.PrevPage
                            onClick={(e) => e.preventDefault()}
                            className={styles['sitecore-pagination-prev-page']}
                          >
                            <ArrowLeftIcon />
                          </Pagination.PrevPage>
                          <Pagination.Pages className={styles['sitecore-pagination-pages']}>
                            {(pagination) =>
                              Pagination.paginationLayout(pagination, {}).map(({ page, type }) =>
                                type === 'page' ? (
                                  <Pagination.Page
                                    key={page}
                                    aria-label={`Page ${page}`}
                                    page={page}
                                    onClick={(e) => e.preventDefault()}
                                    className={styles['sitecore-pagination-page']}
                                  >
                                    {page}
                                  </Pagination.Page>
                                ) : (
                                  <span key={type}>...</span>
                                ),
                              )
                            }
                          </Pagination.Pages>
                          <Pagination.NextPage
                            onClick={(e) => e.preventDefault()}
                            className={styles['sitecore-pagination-next-page']}
                          >
                            <ArrowRightIcon />
                          </Pagination.NextPage>
                        </Pagination.Root>
                      </div>
                    </section>
                  </>
                )}
                {totalItems <= 0 && !isFetching && (
                  <div className={styles['sitecore-no-results']}>
                    <h3>0 Results</h3>
                  </div>
                )}
              </div>
            </div>
          )}
        </>
      );
    };
    const SearchResultsWithLayoutOptionWidget = widget(SearchResultsWithLayoutOptionComponent, WidgetDataType.SEARCH_RESULTS, 'content');
    export default SearchResultsWithLayoutOptionWidget;
    // styles.module.css
    @keyframes rotate-animation {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    
    /* ArticleCardRowRoot */
    .sitecore-article-card-row-root {
      box-sizing: border-box;
      border: solid 1px var(--sdc-palette-grey-400);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      display: flex;
      height: 200px;
      width: 100%;
      padding: var(--sdc-spacing-m);
      margin-bottom: var(--sdc-spacing-m);
      cursor: pointer;
    }
    
    .sitecore-article-card-row-root:focus-within {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-card-row-root:hover {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* ArticleCardRowLeft */
    .sitecore-article-card-row-left {
      position: relative;
      height: 164px;
      width: 164px;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleCardRowRight */
    .sitecore-article-card-row-right {
      box-sizing: border-box;
      padding-left: var(--sdc-spacing-m);
      overflow: hidden;
      max-width: 80%;
    }
    
    /* ArticleCardRowImage */
    .sitecore-article-card-row-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ArticleCardRowTitle */
    .sitecore-article-card-row-title {
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize4-fontSize);
      font-weight: var(--sdc-typography-fontSize4-fontWeight);
      line-height: var(--sdc-typography-fontSize4-lineHeight);
    }
    
    /* ArticleCardRowContent */
    .sitecore-article-card-row-content {
      margin: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: var(--sdc-typography-fontWeight);
      line-height: var(--sdc-typography-lineHeight);
      color: var(--sdc-palette-primary-main);
    }
    
    /* ArticleCardRowId */
    .sitecore-article-card-row-id {
      font-family: var(--sdc-typography-fontFamilySystem);
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* ArticleCardRowLink */
    .sitecore-article-card-row-link {
      text-decoration: none;
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize4-fontSize);
    }
    
    .sitecore-article-card-row-link:hover {
      text-decoration: none;
    }
    
    .sitecore-article-card-row-link:focus {
      text-decoration: none;
    }
    
    /* CardViewSwitcherRoot */
    .sitecore-card-view-switcher-root {
      display: inline-flex;
      background-color: var(--sdc-palette-common-white);
    }
    
    /* CardViewSwitcherItem */
    .sitecore-card-view-switcher-item {
      all: unset;
      align-items: center;
      background-color: var(--sdc-palette-common-white);
      color: var(--sdc-palette-grey-500);
      display: flex;
      height: 30px;
      justify-content: center;
      margin-left: var(--sdc-spacing-s);
      width: 30px;
    }
    
    .sitecore-card-view-switcher-item:first-child {
      margin-left: 0;
    }
    
    .sitecore-card-view-switcher-item:hover {
      background-color: var(--sdc-palette-primary-light);
    }
    
    .sitecore-card-view-switcher-item[data-state='on'] {
      background-color: var(--sdc-palette-primary-main);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-card-view-switcher-item:focus {
      position: relative;
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* Row */
    .sitecore-row {
      width: 100%;
      display: block;
    }
    
    /* BreadcrumbRoot */
    .sitecore-breadcrumb-root {
      width: 100%;
      display: inline-block;
    }
    
    /* BreadcrumbList */
    .sitecore-breadcrumb-list {
      list-style: none;
      padding: 0;
    }
    
    /* BreadcrumbItem */
    .sitecore-breadcrumb-item {
      display: inline;
      color: var(--sdc-palette-primary-main);
      margin-left: var(--sdc-spacing-xs);
    }
    
    /* BreadcrumbNavigation */
    .sitecore-breadcrumb-navigation {
    }
    
    /* BreadcrumbLink */
    .sitecore-breadcrumb-link {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      text-decoration: none;
    }
    
    /* BreadcrumbSeparator */
    .sitecore-breadcrumb-separator {
    }
    
    /* SortSelectTrigger */
    .sitecore-sort-select-trigger {
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: var(--sdc-spacing-xs);
      background-color: transparent;
      height: 40px;
      padding: var(--sdc-spacing-xs) var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      border: none;
    }
    
    .sitecore-sort-select-trigger:focus {
      outline: none;
    }
    
    /* GenericSelectTrigger */
    .sitecore-generic-select-trigger {
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: var(--sdc-spacing-xs);
      background-color: transparent;
      height: 40px;
      padding: var(--sdc-spacing-xs) var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      border: none;
    }
    
    .sitecore-generic-select-trigger:focus {
      outline: none;
    }
    
    /* SortSelectContent */
    .sitecore-sort-select-content {
      background-color: var(--sdc-palette-primary-contrastText);
      overflow: hidden;
      color: var(--sdc-palette-primary-main);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      position: absolute;
      top: 30px;
      z-index: 100;
    }
    
    .sitecore-sort-select-content:focus-within {
      border-color: var(--sdc-palette-primary-dark);
    }
    
    /* GenericSelectContent */
    .sitecore-generic-select-content {
      background-color: var(--sdc-palette-primary-contrastText);
      overflow: hidden;
      color: var(--sdc-palette-primary-main);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      position: absolute;
      top: 30px;
      z-index: 100;
    }
    
    .sitecore-generic-select-content:focus-within {
      border-color: var(--sdc-palette-primary-dark);
    }
    
    /* SortSelectViewport */
    .sitecore-sort-select-viewport {
      padding: var(--sdc-spacing-xs);
      z-index: 50000;
    }
    
    /* GenericSelectViewport */
    .sitecore-generic-select-viewport {
      padding: var(--sdc-spacing-xs);
      z-index: 50000;
    }
    
    /* SortSelectOption */
    .sitecore-sort-select-option {
      display: flex;
      align-items: center;
      line-height: 1;
      cursor: pointer;
      -webkit-user-select: none;
      -moz-user-select: none;
      user-select: none;
      white-space: nowrap;
      height: 25px;
      padding: 0 var(--sdc-spacing-xs);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      position: relative;
      border-radius: 0;
    }
    
    .sitecore-sort-select-option[data-highlighted] {
      border-radius: 0;
      background-color: var(--sdc-palette-primary-dark);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-sort-select-option[data-disabled] {
      color: var(--sdc-palette-grey-800);
      font-style: italic;
    }
    
    /* GenericSelectOption */
    .sitecore-generic-select-option {
      display: flex;
      align-items: center;
      line-height: 1;
      cursor: pointer;
      -webkit-user-select: none;
      -moz-user-select: none;
      user-select: none;
      white-space: nowrap;
      height: 25px;
      padding: 0 var(--sdc-spacing-xs);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      color: var(--sdc-palette-primary-main);
      position: relative;
      border-radius: 0;
    }
    
    .sitecore-generic-select-option[data-highlighted] {
      border-radius: 0;
      background-color: var(--sdc-palette-primary-dark);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    .sitecore-generic-select-option[data-disabled] {
      color: var(--sdc-palette-grey-800);
      font-style: italic;
    }
    
    /* SortSelectValue */
    .sitecore-sort-select-value {
      color: var(--sdc-palette-primary-main);
    }
    
    /* GenericSelectValue */
    .sitecore-generic-select-value {
      color: var(--sdc-palette-primary-main);
    }
    
    /* SortSelectIcon */
    .sitecore-sort-select-icon {
    }
    
    /* GenericSelectIcon */
    .sitecore-generic-select-icon {
    }
    
    /* SortSelectOptionText */
    .sitecore-sort-select-option-text {
    }
    
    /* GenericSelectOptionText */
    .sitecore-generic-select-option-text {
    }
    
    /* ArticleRoot */
    .sitecore-article-root {
      border: solid 1px var(--sdc-palette-grey-400);
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      width: 180px;
      padding: var(--sdc-spacing-m);
      cursor: pointer;
      display: block;
    }
    
    .sitecore-article-root:focus-within {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-root:hover {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* ArticleImage */
    .sitecore-article-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ImageWrapper */
    .sitecore-image-wrapper {
      margin: auto auto 10px;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleTitle */
    .sitecore-article-title {
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize3-fontSize);
      font-weight: var(--sdc-typography-fontSize3-fontWeight);
      line-height: var(--sdc-typography-fontSize3-lineHeight);
    }
    
    /* ArticleContent */
    .sitecore-article-content {
      margin: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: var(--sdc-typography-fontWeight);
      line-height: var(--sdc-typography-lineHeight);
      color: var(--sdc-palette-primary-main);
    }
    
    /* ArticleId */
    .sitecore-article-id {
      font-family: var(--sdc-typography-fontFamilySystem);
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* ArticleLink */
    .sitecore-article-link {
      text-decoration: none;
      color: var(--sdc-palette-primary-main);
      font-size: var(--sdc-typography-fontSize4-fontSize);
    }
    
    .sitecore-article-link:hover {
      text-decoration: none;
    }
    
    .sitecore-article-link:focus {
      text-decoration: none;
    }
    
    /* AccordionItemCheckbox */
    .sitecore-accordion-item-checkbox {
      all: unset;
      background-color: white;
      width: 20px;
      height: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid #ccc;
      border-radius: 4px;
      cursor: pointer;
    }
    
    .sitecore-accordion-item-checkbox[data-state='checked'] {
      color: var(--sdc-palette-primary-contrastText);
      background-color: var(--sdc-palette-primary-main);
    }
    
    .sitecore-accordion-item-checkbox:focus {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    /* AccordionItemToggle */
    .sitecore-accordion-item-toggle {
      all: unset;
      width: 40px;
      height: 35px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: var(--sdc-typography-fontSize1-fontSize);
      margin-right: var(--sdc-spacing-s);
    }
    
    .sitecore-accordion-item-toggle:focus {
      border: solid 1px var(--sdc-palette-primary-main);
    }
    
    .sitecore-accordion-item-toggle[data-state='on'] {
      background-color: var(--sdc-palette-primary-main);
      color: var(--sdc-palette-primary-contrastText);
    }
    
    /* AccordionItemCheckboxIndicator */
    .sitecore-accordion-item-checkbox-indicator {
      color: var(--sdc-palette-primary-contrastText);
      width: 15px;
      height: 15px;
    }
    
    /* AccordionValueList */
    .sitecore-accordion-value-list {
      list-style: none;
    }
    
    .sitecore-accordion-value-list li {
      padding: var(--sdc-spacing-xs) 0;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    .sitecore-accordion-value-list[data-orientation='horizontal'] {
      display: flex;
      flex-direction: row;
    }
    
    /* AccordionItemCheckboxLabel */
    .sitecore-accordion-item-checkbox-label {
      padding-left: var(--sdc-spacing-xs);
      color: var(--sdc-palette-primary-main);
    }
    
    /* AccordionItem */
    .sitecore-accordion-item {
      display: flex;
      align-items: center;
    }
    
    /* AccordionHeader */
    .sitecore-accordion-header {
      display: flex;
      margin-top: var(--sdc-spacing-s);
      margin-bottom: var(--sdc-spacing-s);
    }
    
    /* AccordionTrigger */
    .sitecore-accordion-trigger {
      align-items: center;
      color: var(--sdc-palette-primary-main);
      display: flex;
      font-size: var(--sdc-typography-fontSize2-fontSize);
      height: 45px;
      justify-content: space-between;
      line-height: 1;
      padding: 0 var(--sdc-spacing-m);
      flex: 1 1 0;
      background: none;
      border: none;
    }
    
    /* AccordionFacetsFacet */
    .sitecore-accordion-facets-facet {
      border-bottom: solid 1px var(--sdc-palette-grey-800);
    }
    
    /* AccordionFacetsRoot */
    .sitecore-accordion-facets-root {
    }
    
    /* PaginationRoot */
    .sitecore-pagination-root {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      margin-top: var(--sdc-spacing-m);
    }
    
    /* PaginationPrevPage */
    .sitecore-pagination-prev-page {
      display: inline;
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-prev-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationNextPage */
    .sitecore-pagination-next-page {
      display: inline;
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-next-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationFirstPage */
    .sitecore-pagination-first-page {
      font-size: var(--sdc-typography-fontSize2-fontSize);
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-first-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
      display: none;
    }
    
    /* PaginationLastPage */
    .sitecore-pagination-last-page {
      font-size: var(--sdc-typography-fontSize2-fontSize);
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-last-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
    }
    
    /* PaginationPage */
    .sitecore-pagination-page {
      cursor: pointer;
      margin: 0 5px;
    }
    
    .sitecore-pagination-page[data-current='true'] {
      color: gray;
      pointer-events: none;
      text-decoration-line: none;
    }
    
    /* PaginationPages */
    .sitecore-pagination-pages {
      display: inline;
    }
    
    /* Wrapper */
    .sitecore-wrapper {
    }
    
    /* MainArea */
    .sitecore-main-area {
      display: flex;
      max-width: 100%;
      position: relative;
    }
    
    /* LeftArea */
    .sitecore-left-area {
      position: relative;
      flex: 1 1;
      margin-right: var(--sdc-spacing-l);
    }
    
    /* RightArea */
    .sitecore-right-area {
      display: flex;
      flex-direction: column;
      flex: 4 1;
    }
    
    /* RightTopArea */
    .sitecore-right-top-area {
      display: flex;
      justify-content: space-between;
    }
    
    /* Grid */
    .sitecore-grid {
      width: 100%;
      display: grid;
      grid-gap: var(--sdc-spacing-m);
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      grid-auto-flow: row;
    }
    
    /* ClearFilters */
    .sitecore-clear-filters {
      background: none;
      border: none;
      color: var(--sdc-palette-primary-main);
    }
    
    .sitecore-clear-filters:hover {
      text-decoration: underline;
      cursor: pointer;
    }
    
    /* SelectedFiltersList */
    .sitecore-selected-filters-list {
      width: 100%;
      list-style: none;
      padding-left: var(--sdc-spacing-s);
    }
    
    /* SelectedFiltersListItem */
    .sitecore-selected-filters-list-item {
      display: flex;
      justify-content: space-between;
      padding: var(--sdc-spacing-xs) 0 var(--sdc-spacing-xs) 0;
    }
    
    /* SelectedFiltersListItemText */
    .sitecore-selected-filters-list-item-text {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* SelectedFiltersListItemButton */
    .sitecore-selected-filters-list-item-button {
      background: none;
      border: none;
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      cursor: pointer;
    }
    
    /* PageControls */
    .sitecore-page-controls {
      display: flex;
      justify-content: space-between;
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* QuerySummary */
    .sitecore-query-summary {
      color: var(--sdc-palette-primary-main);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: var(--sdc-typography-fontSize1-fontSize);
      font-weight: bold;
      margin: auto 0;
    }
    
    /* LoaderContainer */
    .sitecore-loader-container {
      align-items: center;
      display: flex;
      position: absolute;
      left: 0;
      right: 0;
      height: 100%;
      background-color: var(--sdc-palette-common-white);
      opacity: 0.5;
      z-index: 100;
    }
    
    /* LoaderAnimation */
    .sitecore-loader-animation {
      animation: rotate-animation 2s linear infinite;
      display: block;
      fill: var(--sdc-palette-primary-main);
      height: 50px;
      margin: auto;
      width: 50px;
    }
    
    /* NoResults */
    .sitecore-no-results {
      display: flex;
      justify-content: center;
      width: 100%;
    }

Options

Default current page

defaultPage sets the page number as the default current page number.

Default: 1

<SearchResultsWithLayoutOption
	rfkId="rfkid_7"
 	defaultPage={3}
/>

Default items per page

defaultItemsPerPage is the default maximum number of items per page.

Default: 24

<SearchResultsWithLayoutOption
	rfkId="rfkid_7"
 	defaultItemsPerPage={48}
/>

Default search keyphrase

defaultKeyphrase is the default search keyphrase.

<SearchResultsWithLayoutOption
	rfkId="rfkid_7"
 	defaultKeyphrase="makeup"
/>

Default sort type

From among the sort types defined in the Sitecore Search user interface, defaultSortType sets a sort type as the default sort type of component.

Default: featured_desc

<SearchResultsWithLayoutOption
	rfkId="rfkid_7"
 	defaultSortType="content_name_asc"
/>
If you have suggestions for improving this article, let us know!