1. Preview search

The preview search widget with menu on the left

The preview search widget template, described in this topic, shows search results based on visitor query and menu options appearing on the left for left-to-right keyboard navigation. Changes in input in the search input field triggers requests to Sitecore Search.

The following sections describe the preview search widget template with menu on the left, 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 preview search 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 preview-search-left --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:

  • In a folder for the widget component, create a file called index.tsx then in the file, paste following code block.

    // index.tsx
    import type { ChangeEvent } from 'react';
    import { useCallback } from 'react';
    
    import type { PreviewSearchInitialState } from '@sitecore-search/react';
    import { WidgetDataType, usePreviewSearch, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence, PreviewSearch } from '@sitecore-search/ui';
    
    type ArticleModel = {
      id: string;
      name: string;
      image_url: string;
      url: string;
      source_id?: string;
    };
    type InitialState = PreviewSearchInitialState<'itemsPerPage' | 'suggestionsList'>;
    export const PreviewSearchLeftComponent = ({ defaultItemsPerPage = 6 }) => {
      const {
        widgetRef,
        actions: { onItemClick, onKeyphraseChange, onSuggestionClick },
        queryResult,
        queryResult: {
          isFetching,
          isLoading,
          data: {
            suggestion: {
              content_name_context_aware: articleSuggestions = [],
              content_trending_category: trendingCategorySuggestions = [],
            } = {},
          } = {},
        },
      } = usePreviewSearch<ArticleModel, InitialState>({
        state: {
          suggestionsList: [
            {
              suggestion: 'content_name_context_aware',
              max: 10,
            },
            {
              suggestion: 'content_trending_category',
              max: 10,
            },
          ],
          itemsPerPage: defaultItemsPerPage,
        },
      });
      const loading = isLoading || isFetching;
      const keyphraseHandler = useCallback(
        (event: ChangeEvent<HTMLInputElement>) => {
          const target = event.target;
          onKeyphraseChange({
            keyphrase: target.value,
          });
        },
        [onKeyphraseChange],
      );
      return (
        <PreviewSearch.Root>
          <PreviewSearch.Input onChange={keyphraseHandler} autoComplete="off" placeholder="Type to search..." />
          <PreviewSearch.Content ref={widgetRef}>
            <Presence present={loading}>
              <div>
                <svg aria-busy={loading} aria-hidden={!loading} 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>
              </div>
            </Presence>
            <Presence present={!loading}>
              <>
                {trendingCategorySuggestions.length + articleSuggestions.length > 0 && (
                  <PreviewSearch.Suggestions>
                    {trendingCategorySuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup id="article_trending_category" filterAttribute="category_name">
                        <h2>Trending Categories</h2>
                        {trendingCategorySuggestions.slice(0, 1).map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger id={text} key={text} asChild>
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_trending_category',
                                  title: 'Trending Categories',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                    {articleSuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup id="article_name_context_aware">
                        <h2>Suggestions</h2>
                        {articleSuggestions.map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger id={text} key={text} asChild>
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_name_context_aware',
                                  title: 'Suggestions',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                  </PreviewSearch.Suggestions>
                )}
                <PreviewSearch.Results defaultQueryResult={queryResult}>
                  {({ isFetching: loading, data: { content: articles = [] } = {} }) => (
                    <PreviewSearch.Items data-loading={loading}>
                      <Presence present={loading}>
                        <div>
                          <svg
                            aria-busy={loading}
                            aria-hidden={!loading}
                            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>
                        </div>
                      </Presence>
                      {!loading &&
                        articles.map((article, index) => (
                          <PreviewSearch.Item key={article.id} asChild>
                            <a
                              href={article.url}
                              onClick={(e) => {
                                e.preventDefault();
                                onItemClick({
                                  id: article.id,
                                  index,
                                  sourceId: article.source_id,
                                });
                                // add redirection or any action
                              }}
                            >
                              <ArticleCard.Root>
                                <div>
                                  <ArticleCard.Image src={article.image_url} />
                                </div>
                                <ArticleCard.Title>{article.name}</ArticleCard.Title>
                              </ArticleCard.Root>
                            </a>
                          </PreviewSearch.Item>
                        ))}
                    </PreviewSearch.Items>
                  )}
                </PreviewSearch.Results>
              </>
            </Presence>
          </PreviewSearch.Content>
        </PreviewSearch.Root>
      );
    };
    const PreviewSearchLeftWidget = widget(PreviewSearchLeftComponent, WidgetDataType.PREVIEW_SEARCH, 'content');
    export default PreviewSearchLeftWidget;

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 preview-search-left --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:

  • In a folder for the widget component, create a file called index.jsx then in the file, paste following code block.

    // index.jsx
    import { useCallback } from 'react';
    
    import { WidgetDataType, usePreviewSearch, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence, PreviewSearch } from '@sitecore-search/ui';
    
    export const PreviewSearchLeftComponent = ({ defaultItemsPerPage = 6 }) => {
      const {
        widgetRef,
        actions: { onItemClick, onKeyphraseChange, onSuggestionClick },
        queryResult,
        queryResult: {
          isFetching,
          isLoading,
          data: {
            suggestion: {
              content_name_context_aware: articleSuggestions = [],
              content_trending_category: trendingCategorySuggestions = [],
            } = {},
          } = {},
        },
      } = usePreviewSearch({
        state: {
          suggestionsList: [
            {
              suggestion: 'content_name_context_aware',
              max: 10,
            },
            {
              suggestion: 'content_trending_category',
              max: 10,
            },
          ],
          itemsPerPage: defaultItemsPerPage,
        },
      });
      const loading = isLoading || isFetching;
      const keyphraseHandler = useCallback(
        (event) => {
          const target = event.target;
          onKeyphraseChange({
            keyphrase: target.value,
          });
        },
        [onKeyphraseChange],
      );
      return (
        <PreviewSearch.Root>
          <PreviewSearch.Input onChange={keyphraseHandler} autoComplete="off" placeholder="Type to search..." />
          <PreviewSearch.Content ref={widgetRef}>
            <Presence present={loading}>
              <div>
                <svg aria-busy={loading} aria-hidden={!loading} 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>
              </div>
            </Presence>
            <Presence present={!loading}>
              <>
                {trendingCategorySuggestions.length + articleSuggestions.length > 0 && (
                  <PreviewSearch.Suggestions>
                    {trendingCategorySuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup id="article_trending_category" filterAttribute="category_name">
                        <h2>Trending Categories</h2>
                        {trendingCategorySuggestions.slice(0, 1).map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger id={text} key={text} asChild>
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_trending_category',
                                  title: 'Trending Categories',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                    {articleSuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup id="article_name_context_aware">
                        <h2>Suggestions</h2>
                        {articleSuggestions.map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger id={text} key={text} asChild>
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_name_context_aware',
                                  title: 'Suggestions',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                  </PreviewSearch.Suggestions>
                )}
                <PreviewSearch.Results defaultQueryResult={queryResult}>
                  {({ isFetching: loading, data: { content: articles = [] } = {} }) => (
                    <PreviewSearch.Items data-loading={loading}>
                      <Presence present={loading}>
                        <div>
                          <svg
                            aria-busy={loading}
                            aria-hidden={!loading}
                            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>
                        </div>
                      </Presence>
                      {!loading &&
                        articles.map((article, index) => (
                          <PreviewSearch.Item key={article.id} asChild>
                            <a
                              href={article.url}
                              onClick={(e) => {
                                e.preventDefault();
                                onItemClick({
                                  id: article.id,
                                  index,
                                  sourceId: article.source_id,
                                });
                                // add redirection or any action
                              }}
                            >
                              <ArticleCard.Root>
                                <div>
                                  <ArticleCard.Image src={article.image_url} />
                                </div>
                                <ArticleCard.Title>{article.name}</ArticleCard.Title>
                              </ArticleCard.Root>
                            </a>
                          </PreviewSearch.Item>
                        ))}
                    </PreviewSearch.Items>
                  )}
                </PreviewSearch.Results>
              </>
            </Presence>
          </PreviewSearch.Content>
        </PreviewSearch.Root>
      );
    };
    const PreviewSearchLeftWidget = widget(PreviewSearchLeftComponent, WidgetDataType.PREVIEW_SEARCH, 'content');
    export default PreviewSearchLeftWidget;

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 preview-search-left --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 type { ChangeEvent } from 'react';
    import { useCallback } from 'react';
    
    import type { PreviewSearchInitialState } from '@sitecore-search/react';
    import { WidgetDataType, usePreviewSearch, widget } from '@sitecore-search/react';
    import { Presence, PreviewSearch } from '@sitecore-search/ui';
    
    import {
      ArticleCardStyled,
      LoaderAnimation,
      LoaderContainer,
      PreviewSearchStyled,
      SearchGroupHeadingStyled,
    } from './styled';
    
    type ArticleModel = {
      id: string;
      name: string;
      image_url: string;
      url: string;
      source_id?: string;
    };
    
    type InitialState = PreviewSearchInitialState<'itemsPerPage' | 'suggestionsList'>;
    
    export const PreviewSearchLeftComponent = ({ defaultItemsPerPage = 6 }) => {
      const {
        widgetRef,
        actions: { onItemClick, onKeyphraseChange, onSuggestionClick },
        queryResult,
        queryResult: {
          isFetching,
          isLoading,
          data: {
            suggestion: {
              content_name_context_aware: articleSuggestions = [],
              content_trending_category: trendingCategorySuggestions = [],
            } = {},
          } = {},
        },
      } = usePreviewSearch<ArticleModel, InitialState>({
        state: {
          suggestionsList: [
            { suggestion: 'content_name_context_aware', max: 10 },
            { suggestion: 'content_trending_category', max: 10 },
          ],
          itemsPerPage: defaultItemsPerPage,
        },
      });
    
      const loading = isLoading || isFetching;
      const keyphraseHandler = useCallback(
        (event: ChangeEvent<HTMLInputElement>) => {
          const target = event.target;
          onKeyphraseChange({ keyphrase: target.value });
        },
        [onKeyphraseChange],
      );
      return (
        <PreviewSearchStyled.Root>
          <PreviewSearchStyled.Input onChange={keyphraseHandler} autoComplete="off" placeholder="Type to search..." />
          <PreviewSearchStyled.Content ref={widgetRef}>
            <Presence present={loading}>
              <LoaderContainer>
                <LoaderAnimation
                  aria-busy={loading}
                  aria-hidden={!loading}
                  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>
              </LoaderContainer>
            </Presence>
            <Presence present={!loading}>
              <>
                {trendingCategorySuggestions.length + articleSuggestions.length > 0 && (
                  <PreviewSearchStyled.Suggestions>
                    {trendingCategorySuggestions.length > 0 && (
                      <PreviewSearchStyled.SuggestionsGroup id="article_trending_category" filterAttribute="category_name">
                        <SearchGroupHeadingStyled>Trending Categories</SearchGroupHeadingStyled>
                        {trendingCategorySuggestions.slice(0, 1).map(({ text }) => (
                          <PreviewSearchStyled.SuggestionTrigger id={text} key={text} asChild>
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_trending_category',
                                  title: 'Trending Categories',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearchStyled.SuggestionTrigger>
                        ))}
                      </PreviewSearchStyled.SuggestionsGroup>
                    )}
                    {articleSuggestions.length > 0 && (
                      <PreviewSearchStyled.SuggestionsGroup id="article_name_context_aware">
                        <SearchGroupHeadingStyled>Suggestions</SearchGroupHeadingStyled>
                        {articleSuggestions.map(({ text }) => (
                          <PreviewSearchStyled.SuggestionTrigger id={text} key={text} asChild>
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_name_context_aware',
                                  title: 'Suggestions',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearchStyled.SuggestionTrigger>
                        ))}
                      </PreviewSearchStyled.SuggestionsGroup>
                    )}
                  </PreviewSearchStyled.Suggestions>
                )}
                <PreviewSearch.Results defaultQueryResult={queryResult}>
                  {({ isFetching: loading, data: { content: articles = [] } = {} }) => (
                    <PreviewSearchStyled.Items data-loading={loading}>
                      <Presence present={loading}>
                        <LoaderContainer>
                          <LoaderAnimation
                            aria-busy={loading}
                            aria-hidden={!loading}
                            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>
                        </LoaderContainer>
                      </Presence>
                      {!loading &&
                        articles.map((article, index) => (
                          <PreviewSearchStyled.Item key={article.id} asChild>
                            <PreviewSearchStyled.Link
                              href={article.url}
                              onClick={(e) => {
                                e.preventDefault();
                                onItemClick({ id: article.id, index, sourceId: article.source_id });
                                // add redirection or any action
                              }}
                            >
                              <ArticleCardStyled.Root>
                                <ArticleCardStyled.ImageWrapper>
                                  <ArticleCardStyled.Image src={article.image_url} />
                                </ArticleCardStyled.ImageWrapper>
                                <ArticleCardStyled.Name>{article.name}</ArticleCardStyled.Name>
                              </ArticleCardStyled.Root>
                            </PreviewSearchStyled.Link>
                          </PreviewSearchStyled.Item>
                        ))}
                    </PreviewSearchStyled.Items>
                  )}
                </PreviewSearch.Results>
              </>
            </Presence>
          </PreviewSearchStyled.Content>
        </PreviewSearchStyled.Root>
      );
    };
    const PreviewSearchLeftWidget = widget(PreviewSearchLeftComponent, WidgetDataType.PREVIEW_SEARCH, 'content');
    export default PreviewSearchLeftWidget;
    // styled.ts
    import { keyframes, styled } from 'styled-components';
    
    import { ArticleCard, PreviewSearch, theme } from '@sitecore-search/ui';
    
    export const PreviewSearchInput = styled(PreviewSearch.Input)`
      width: 800px;
      box-sizing: border-box;
      padding: ${theme.vars.spacing.xs};
    
      &:focus {
        outline: 1px solid ${theme.vars.palette.grey['400']};
      }
    `;
    
    export const PreviewSearchContent = styled(PreviewSearch.Content)`
      animation-duration: 500ms;
      animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
      will-change: transform, opacity;
      width: var(--radix-popover-trigger-width);
      max-height: var(--radix-popover-content-available-height);
      background: ${theme.vars.palette.primary.light};
      box-shadow: 2px 5px 5px 5px ${theme.vars.palette.grey['400']};
      display: flex;
      justify-content: center;
      height: 400px;
      padding-top: 0;
      font-family: ${theme.vars.typography.fontFamilySystem};
    
      @keyframes slide-up-and-fade {
        from {
          opacity: 0;
          transform: translateY(2px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }
    
      @keyframes slide-right-and-fade {
        from {
          opacity: 0;
          transform: translateX(-2px);
        }
        to {
          opacity: 1;
          transform: translateX(0);
        }
      }
    
      @keyframes slide-down-and-fade {
        from {
          opacity: 0;
          transform: translateY(-2px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }
    
      @keyframes slide-left-and-fade {
        from {
          opacity: 0;
          transform: translateX(2px);
        }
        to {
          opacity: 1;
          transform: translateX(0);
        }
      }
    
      &[data-state='open'][data-side='top'] {
        animation-name: slide-down-and-fade;
      }
    
      &[data-state='open'][data-side='right'] {
        animation-name: slide-left-and-fade;
      }
    
      &[data-state='open'][data-side='bottom'] {
        animation-name: slide-up-and-fade;
      }
    
      &[data-state='open'][data-side='left'] {
        animation-name: slide-right-and-fade;
      }
    `;
    
    const PreviewSearchSuggestions = styled(PreviewSearch.Suggestions)`
      display: block;
      box-sizing: border-box;
      list-style: none;
      width: 16rem;
      font-size: ${theme.vars.typography.fontSize1.fontSize};
    `;
    
    const PreviewSearchSuggestionItem = styled(PreviewSearch.SuggestionItem)`
      padding: ${theme.vars.spacing.s} ${theme.vars.spacing.s};
    
      &:focus,
      &:hover {
        outline: none;
        font-weight: bold;
        color: ${theme.vars.palette.primary.main};
        background: #fff;
      }
    `;
    
    const PreviewSearchSuggestionTrigger = styled(PreviewSearch.SuggestionTrigger)`
      cursor: pointer;
      padding: ${theme.vars.spacing.s} ${theme.vars.spacing.s};
    
      &[data-state='active'],
      &:focus,
      &:hover {
        outline: none;
        font-weight: bold;
        color: ${theme.vars.palette.primary.main};
        background: #fff;
      }
    `;
    
    const PreviewSearchSuggestionsGroup = styled(PreviewSearch.SuggestionsGroup)`
      flex: 1;
      display: flex;
      flex-direction: column;
    `;
    
    const PreviewSearchLink = styled.a`
      color: ${theme.vars.palette.primary.main};
      display: flex;
      box-sizing: border-box;
      text-decoration: none;
      width: 100%;
      &:focus {
        box-shadow: 2px 2px 4px ${theme.vars.palette.primary.main};
      }
    `;
    
    const PreviewSearchItems = styled(PreviewSearch.Items)`
      flex: 3;
      background: #fff;
      overflow-y: auto;
      display: flex;
    
      &[data-loading='false'] {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        list-style: none;
        margin: 0;
        padding: ${theme.vars.spacing.s};
        gap: ${theme.vars.spacing.m};
      }
    `;
    
    const PreviewSearchRoot = styled(PreviewSearch.Root)``;
    const PreviewSearchItem = styled(PreviewSearch.Item)``;
    
    export const PreviewSearchStyled = {
      Root: PreviewSearchRoot,
      Input: PreviewSearchInput,
      Content: PreviewSearchContent,
      Items: PreviewSearchItems,
      Item: PreviewSearchItem,
      Suggestions: PreviewSearchSuggestions,
      SuggestionsGroup: PreviewSearchSuggestionsGroup,
      SuggestionItem: PreviewSearchSuggestionItem,
      SuggestionTrigger: PreviewSearchSuggestionTrigger,
      Link: PreviewSearchLink,
    };
    
    const ArticleRootStyled = styled(ArticleCard.Root)`
      box-shadow: 2px 2px 4px ${theme.vars.palette?.grey?.['400']};
      padding: ${theme.vars.spacing?.s};
      cursor: pointer;
      display: block;
      border: solid 1px transparent;
      text-align: center;
    
      &:focus-within {
        box-shadow: 2px 2px 4px ${theme.vars.palette?.primary?.main};
      }
    
      &:hover {
        box-shadow: 2px 2px 4px ${theme.vars.palette?.primary?.main};
      }
    `;
    
    const ArticleImageStyled = styled(ArticleCard.Image)`
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    `;
    
    const ArticleImageWrapperStyled = styled.div`
      margin: auto auto 10px;
      position: relative;
      height: 6rem;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    `;
    
    const ArticleNameStyled = styled(ArticleCard.Title)`
      max-height: 2rem;
      overflow: hidden;
      margin: 0 0 ${theme.vars.spacing?.m};
      font-family: ${theme.vars.typography?.fontFamilySystem};
      font-size: 0.8rem;
      font-weight: ${theme.vars.typography?.fontSize4?.fontWeight};
    `;
    
    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 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 = {
      Root: ArticleRootStyled,
      Link: ArticleLinkStyled,
      Content: ArticleContentStyled,
      ImageWrapper: ArticleImageWrapperStyled,
      Image: ArticleImageStyled,
      Name: ArticleNameStyled,
    };
    
    // misc
    export const LoaderContainer = styled.div`
      align-items: center;
      display: flex;
      flex: 1;
    `;
    
    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 SearchGroupHeadingStyled = styled.h2`
      box-sizing: border-box;
      padding-left: ${theme.vars.spacing.s};
    `;

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 preview-search-left --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 { useCallback } from 'react';
    
    import { WidgetDataType, usePreviewSearch, widget } from '@sitecore-search/react';
    import { Presence, PreviewSearch } from '@sitecore-search/ui';
    
    import {
      ArticleCardStyled,
      LoaderAnimation,
      LoaderContainer,
      PreviewSearchStyled,
      SearchGroupHeadingStyled,
    } from './styled';
    
    export const PreviewSearchLeftComponent = ({ defaultItemsPerPage = 6 }) => {
      const {
        widgetRef,
        actions: { onItemClick, onKeyphraseChange, onSuggestionClick },
        queryResult,
        queryResult: {
          isFetching,
          isLoading,
          data: {
            suggestion: {
              content_name_context_aware: articleSuggestions = [],
              content_trending_category: trendingCategorySuggestions = [],
            } = {},
          } = {},
        },
      } = usePreviewSearch({
        state: {
          suggestionsList: [
            {
              suggestion: 'content_name_context_aware',
              max: 10,
            },
            {
              suggestion: 'content_trending_category',
              max: 10,
            },
          ],
          itemsPerPage: defaultItemsPerPage,
        },
      });
      const loading = isLoading || isFetching;
      const keyphraseHandler = useCallback(
        (event) => {
          const target = event.target;
          onKeyphraseChange({
            keyphrase: target.value,
          });
        },
        [onKeyphraseChange],
      );
      return (
        <PreviewSearchStyled.Root>
          <PreviewSearchStyled.Input onChange={keyphraseHandler} autoComplete="off" placeholder="Type to search..." />
          <PreviewSearchStyled.Content ref={widgetRef}>
            <Presence present={loading}>
              <LoaderContainer>
                <LoaderAnimation
                  aria-busy={loading}
                  aria-hidden={!loading}
                  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>
              </LoaderContainer>
            </Presence>
            <Presence present={!loading}>
              <>
                {trendingCategorySuggestions.length + articleSuggestions.length > 0 && (
                  <PreviewSearchStyled.Suggestions>
                    {trendingCategorySuggestions.length > 0 && (
                      <PreviewSearchStyled.SuggestionsGroup id="article_trending_category" filterAttribute="category_name">
                        <SearchGroupHeadingStyled>Trending Categories</SearchGroupHeadingStyled>
                        {trendingCategorySuggestions.slice(0, 1).map(({ text }) => (
                          <PreviewSearchStyled.SuggestionTrigger id={text} key={text} asChild>
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_trending_category',
                                  title: 'Trending Categories',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearchStyled.SuggestionTrigger>
                        ))}
                      </PreviewSearchStyled.SuggestionsGroup>
                    )}
                    {articleSuggestions.length > 0 && (
                      <PreviewSearchStyled.SuggestionsGroup id="article_name_context_aware">
                        <SearchGroupHeadingStyled>Suggestions</SearchGroupHeadingStyled>
                        {articleSuggestions.map(({ text }) => (
                          <PreviewSearchStyled.SuggestionTrigger id={text} key={text} asChild>
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_name_context_aware',
                                  title: 'Suggestions',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearchStyled.SuggestionTrigger>
                        ))}
                      </PreviewSearchStyled.SuggestionsGroup>
                    )}
                  </PreviewSearchStyled.Suggestions>
                )}
                <PreviewSearch.Results defaultQueryResult={queryResult}>
                  {({ isFetching: loading, data: { content: articles = [] } = {} }) => (
                    <PreviewSearchStyled.Items data-loading={loading}>
                      <Presence present={loading}>
                        <LoaderContainer>
                          <LoaderAnimation
                            aria-busy={loading}
                            aria-hidden={!loading}
                            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>
                        </LoaderContainer>
                      </Presence>
                      {!loading &&
                        articles.map((article, index) => (
                          <PreviewSearchStyled.Item key={article.id} asChild>
                            <PreviewSearchStyled.Link
                              href={article.url}
                              onClick={(e) => {
                                e.preventDefault();
                                onItemClick({
                                  id: article.id,
                                  index,
                                  sourceId: article.source_id,
                                });
                                // add redirection or any action
                              }}
                            >
                              <ArticleCardStyled.Root>
                                <ArticleCardStyled.ImageWrapper>
                                  <ArticleCardStyled.Image src={article.image_url} />
                                </ArticleCardStyled.ImageWrapper>
                                <ArticleCardStyled.Name>{article.name}</ArticleCardStyled.Name>
                              </ArticleCardStyled.Root>
                            </PreviewSearchStyled.Link>
                          </PreviewSearchStyled.Item>
                        ))}
                    </PreviewSearchStyled.Items>
                  )}
                </PreviewSearch.Results>
              </>
            </Presence>
          </PreviewSearchStyled.Content>
        </PreviewSearchStyled.Root>
      );
    };
    const PreviewSearchLeftWidget = widget(PreviewSearchLeftComponent, WidgetDataType.PREVIEW_SEARCH, 'content');
    export default PreviewSearchLeftWidget;
    // styled.js
    import { keyframes, styled } from 'styled-components';
    
    import { ArticleCard, PreviewSearch, theme } from '@sitecore-search/ui';
    
    export const PreviewSearchInput = styled(PreviewSearch.Input)`
      width: 800px;
      box-sizing: border-box;
      padding: ${theme.vars.spacing.xs};
    
      &:focus {
        outline: 1px solid ${theme.vars.palette.grey['400']};
      }
    `;
    export const PreviewSearchContent = styled(PreviewSearch.Content)`
      animation-duration: 500ms;
      animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
      will-change: transform, opacity;
      width: var(--radix-popover-trigger-width);
      max-height: var(--radix-popover-content-available-height);
      background: ${theme.vars.palette.primary.light};
      box-shadow: 2px 5px 5px 5px ${theme.vars.palette.grey['400']};
      display: flex;
      justify-content: center;
      height: 400px;
      padding-top: 0;
      font-family: ${theme.vars.typography.fontFamilySystem};
    
      @keyframes slide-up-and-fade {
        from {
          opacity: 0;
          transform: translateY(2px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }
    
      @keyframes slide-right-and-fade {
        from {
          opacity: 0;
          transform: translateX(-2px);
        }
        to {
          opacity: 1;
          transform: translateX(0);
        }
      }
    
      @keyframes slide-down-and-fade {
        from {
          opacity: 0;
          transform: translateY(-2px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }
    
      @keyframes slide-left-and-fade {
        from {
          opacity: 0;
          transform: translateX(2px);
        }
        to {
          opacity: 1;
          transform: translateX(0);
        }
      }
    
      &[data-state='open'][data-side='top'] {
        animation-name: slide-down-and-fade;
      }
    
      &[data-state='open'][data-side='right'] {
        animation-name: slide-left-and-fade;
      }
    
      &[data-state='open'][data-side='bottom'] {
        animation-name: slide-up-and-fade;
      }
    
      &[data-state='open'][data-side='left'] {
        animation-name: slide-right-and-fade;
      }
    `;
    const PreviewSearchSuggestions = styled(PreviewSearch.Suggestions)`
      display: block;
      box-sizing: border-box;
      list-style: none;
      width: 16rem;
      font-size: ${theme.vars.typography.fontSize1.fontSize};
    `;
    const PreviewSearchSuggestionItem = styled(PreviewSearch.SuggestionItem)`
      padding: ${theme.vars.spacing.s} ${theme.vars.spacing.s};
    
      &:focus,
      &:hover {
        outline: none;
        font-weight: bold;
        color: ${theme.vars.palette.primary.main};
        background: #fff;
      }
    `;
    const PreviewSearchSuggestionTrigger = styled(PreviewSearch.SuggestionTrigger)`
      cursor: pointer;
      padding: ${theme.vars.spacing.s} ${theme.vars.spacing.s};
    
      &[data-state='active'],
      &:focus,
      &:hover {
        outline: none;
        font-weight: bold;
        color: ${theme.vars.palette.primary.main};
        background: #fff;
      }
    `;
    const PreviewSearchSuggestionsGroup = styled(PreviewSearch.SuggestionsGroup)`
      flex: 1;
      display: flex;
      flex-direction: column;
    `;
    const PreviewSearchLink = styled.a`
      color: ${theme.vars.palette.primary.main};
      display: flex;
      box-sizing: border-box;
      text-decoration: none;
      width: 100%;
      &:focus {
        box-shadow: 2px 2px 4px ${theme.vars.palette.primary.main};
      }
    `;
    const PreviewSearchItems = styled(PreviewSearch.Items)`
      flex: 3;
      background: #fff;
      overflow-y: auto;
      display: flex;
    
      &[data-loading='false'] {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        list-style: none;
        margin: 0;
        padding: ${theme.vars.spacing.s};
        gap: ${theme.vars.spacing.m};
      }
    `;
    const PreviewSearchRoot = styled(PreviewSearch.Root)``;
    const PreviewSearchItem = styled(PreviewSearch.Item)``;
    export const PreviewSearchStyled = {
      Root: PreviewSearchRoot,
      Input: PreviewSearchInput,
      Content: PreviewSearchContent,
      Items: PreviewSearchItems,
      Item: PreviewSearchItem,
      Suggestions: PreviewSearchSuggestions,
      SuggestionsGroup: PreviewSearchSuggestionsGroup,
      SuggestionItem: PreviewSearchSuggestionItem,
      SuggestionTrigger: PreviewSearchSuggestionTrigger,
      Link: PreviewSearchLink,
    };
    const ArticleRootStyled = styled(ArticleCard.Root)`
      box-shadow: 2px 2px 4px ${theme.vars.palette?.grey?.['400']};
      padding: ${theme.vars.spacing?.s};
      cursor: pointer;
      display: block;
      border: solid 1px transparent;
      text-align: center;
    
      &:focus-within {
        box-shadow: 2px 2px 4px ${theme.vars.palette?.primary?.main};
      }
    
      &:hover {
        box-shadow: 2px 2px 4px ${theme.vars.palette?.primary?.main};
      }
    `;
    const ArticleImageStyled = styled(ArticleCard.Image)`
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    `;
    const ArticleImageWrapperStyled = styled.div`
      margin: auto auto 10px;
      position: relative;
      height: 6rem;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    `;
    const ArticleNameStyled = styled(ArticleCard.Title)`
      max-height: 2rem;
      overflow: hidden;
      margin: 0 0 ${theme.vars.spacing?.m};
      font-family: ${theme.vars.typography?.fontFamilySystem};
      font-size: 0.8rem;
      font-weight: ${theme.vars.typography?.fontSize4?.fontWeight};
    `;
    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 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 = {
      Root: ArticleRootStyled,
      Link: ArticleLinkStyled,
      Content: ArticleContentStyled,
      ImageWrapper: ArticleImageWrapperStyled,
      Image: ArticleImageStyled,
      Name: ArticleNameStyled,
    };
    
    // misc
    export const LoaderContainer = styled.div`
      align-items: center;
      display: flex;
      flex: 1;
    `;
    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 SearchGroupHeadingStyled = styled.h2`
      box-sizing: border-box;
      padding-left: ${theme.vars.spacing.s};
    `;

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. 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.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 preview-search-left --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:

  • 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 type { ChangeEvent } from 'react';
    import { useCallback } from 'react';
    
    import type { PreviewSearchInitialState } from '@sitecore-search/react';
    import { WidgetDataType, usePreviewSearch, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence, PreviewSearch } from '@sitecore-search/ui';
    
    import './styles.css';
    
    type ArticleModel = {
      id: string;
      name: string;
      image_url: string;
      url: string;
      source_id?: string;
    };
    type InitialState = PreviewSearchInitialState<'itemsPerPage' | 'suggestionsList'>;
    export const PreviewSearchLeftComponent = ({ defaultItemsPerPage = 6 }) => {
      const {
        widgetRef,
        actions: { onItemClick, onKeyphraseChange, onSuggestionClick },
        queryResult,
        queryResult: {
          isFetching,
          isLoading,
          data: {
            suggestion: {
              content_name_context_aware: articleSuggestions = [],
              content_trending_category: trendingCategorySuggestions = [],
            } = {},
          } = {},
        },
      } = usePreviewSearch<ArticleModel, InitialState>({
        state: {
          suggestionsList: [
            {
              suggestion: 'content_name_context_aware',
              max: 10,
            },
            {
              suggestion: 'content_trending_category',
              max: 10,
            },
          ],
          itemsPerPage: defaultItemsPerPage,
        },
      });
      const loading = isLoading || isFetching;
      const keyphraseHandler = useCallback(
        (event: ChangeEvent<HTMLInputElement>) => {
          const target = event.target;
          onKeyphraseChange({
            keyphrase: target.value,
          });
        },
        [onKeyphraseChange],
      );
      return (
        <PreviewSearch.Root className="sitecore-preview-search-root">
          <PreviewSearch.Input
            onChange={keyphraseHandler}
            autoComplete="off"
            placeholder="Type to search..."
            className="sitecore-preview-search-input"
          />
          <PreviewSearch.Content ref={widgetRef} className="sitecore-preview-search-content">
            <Presence present={loading}>
              <div className="sitecore-loader-container">
                <svg
                  aria-busy={loading}
                  aria-hidden={!loading}
                  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>
              </div>
            </Presence>
            <Presence present={!loading}>
              <>
                {trendingCategorySuggestions.length + articleSuggestions.length > 0 && (
                  <PreviewSearch.Suggestions className="sitecore-preview-search-suggestions">
                    {trendingCategorySuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup
                        id="article_trending_category"
                        filterAttribute="category_name"
                        className="sitecore-preview-search-suggestions-group"
                      >
                        <h2 className="sitecore-search-group-heading">Trending Categories</h2>
                        {trendingCategorySuggestions.slice(0, 1).map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger
                            id={text}
                            key={text}
                            asChild
                            className="sitecore-preview-search-suggestion-trigger"
                          >
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_trending_category',
                                  title: 'Trending Categories',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                    {articleSuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup
                        id="article_name_context_aware"
                        className="sitecore-preview-search-suggestions-group"
                      >
                        <h2 className="sitecore-search-group-heading">Suggestions</h2>
                        {articleSuggestions.map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger
                            id={text}
                            key={text}
                            asChild
                            className="sitecore-preview-search-suggestion-trigger"
                          >
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_name_context_aware',
                                  title: 'Suggestions',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                  </PreviewSearch.Suggestions>
                )}
                <PreviewSearch.Results defaultQueryResult={queryResult}>
                  {({ isFetching: loading, data: { content: articles = [] } = {} }) => (
                    <PreviewSearch.Items data-loading={loading} className="sitecore-preview-search-items">
                      <Presence present={loading}>
                        <div className="sitecore-loader-container">
                          <svg
                            aria-busy={loading}
                            aria-hidden={!loading}
                            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>
                        </div>
                      </Presence>
                      {!loading &&
                        articles.map((article, index) => (
                          <PreviewSearch.Item key={article.id} asChild className="sitecore-preview-search-item">
                            <a
                              href={article.url}
                              onClick={(e) => {
                                e.preventDefault();
                                onItemClick({
                                  id: article.id,
                                  index,
                                  sourceId: article.source_id,
                                });
                                // add redirection or any action
                              }}
                              className="sitecore-preview-search-link"
                            >
                              <ArticleCard.Root className="sitecore-article-root">
                                <div className="sitecore-article-image-wrapper">
                                  <ArticleCard.Image src={article.image_url} className="sitecore-article-image" />
                                </div>
                                <ArticleCard.Title className="sitecore-article-name">{article.name}</ArticleCard.Title>
                              </ArticleCard.Root>
                            </a>
                          </PreviewSearch.Item>
                        ))}
                    </PreviewSearch.Items>
                  )}
                </PreviewSearch.Results>
              </>
            </Presence>
          </PreviewSearch.Content>
        </PreviewSearch.Root>
      );
    };
    const PreviewSearchLeftWidget = widget(PreviewSearchLeftComponent, WidgetDataType.PREVIEW_SEARCH, 'content');
    export default PreviewSearchLeftWidget;
    // styles.css
    @keyframes rotate-animation {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    
    /* PreviewSearchInput */
    .sitecore-preview-search-input {
      width: 800px;
      box-sizing: border-box;
      padding: var(--sdc-spacing-xs);
    }
    
    .sitecore-preview-search-input:focus {
      outline: 1px solid var(--sdc-palette-grey-400);
    }
    
    /* PreviewSearchContent */
    .sitecore-preview-search-content {
      animation-duration: 500ms;
      animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
      will-change: transform, opacity;
      width: var(--radix-popover-trigger-width);
      max-height: var(--radix-popover-content-available-height);
      background: var(--sdc-palette-primary-light);
      box-shadow: 2px 5px 5px 5px var(--sdc-palette-grey-400);
      display: flex;
      justify-content: center;
      height: 400px;
      padding-top: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
    }
    
    @keyframes slide-up-and-fade {
      from {
        opacity: 0;
        transform: translateY(2px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    @keyframes slide-right-and-fade {
      from {
        opacity: 0;
        transform: translateX(-2px);
      }
      to {
        opacity: 1;
        transform: translateX(0);
      }
    }
    
    @keyframes slide-down-and-fade {
      from {
        opacity: 0;
        transform: translateY(-2px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    @keyframes slide-left-and-fade {
      from {
        opacity: 0;
        transform: translateX(2px);
      }
      to {
        opacity: 1;
        transform: translateX(0);
      }
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='top'] {
      animation-name: slide-down-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='right'] {
      animation-name: slide-left-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='bottom'] {
      animation-name: slide-up-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='left'] {
      animation-name: slide-right-and-fade;
    }
    
    /* PreviewSearchSuggestions */
    .sitecore-preview-search-suggestions {
      display: block;
      box-sizing: border-box;
      list-style: none;
      width: 16rem;
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* PreviewSearchSuggestionItem */
    .sitecore-preview-search-suggestion-item {
      padding: var(--sdc-spacing-s) var(--sdc-spacing-s);
    }
    
    .sitecore-preview-search-suggestion-item:focus,
    .sitecore-preview-search-suggestion-item:hover {
      outline: none;
      font-weight: bold;
      color: var(--sdc-palette-primary-main);
      background: #fff;
    }
    
    /* PreviewSearchSuggestionTrigger */
    .sitecore-preview-search-suggestion-trigger {
      cursor: pointer;
      padding: var(--sdc-spacing-s) var(--sdc-spacing-s);
    }
    
    .sitecore-preview-search-suggestion-trigger[data-state='active'],
    .sitecore-preview-search-suggestion-trigger:focus,
    .sitecore-preview-search-suggestion-trigger:hover {
      outline: none;
      font-weight: bold;
      color: var(--sdc-palette-primary-main);
      background: #fff;
    }
    
    /* PreviewSearchSuggestionsGroup */
    .sitecore-preview-search-suggestions-group {
      flex: 1;
      display: flex;
      flex-direction: column;
    }
    
    /* PreviewSearchLink */
    .sitecore-preview-search-link {
      color: var(--sdc-palette-primary-main);
      display: flex;
      box-sizing: border-box;
      text-decoration: none;
      width: 100%;
    }
    
    .sitecore-preview-search-link:focus {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    /* PreviewSearchItems */
    .sitecore-preview-search-items {
      flex: 3;
      background: #fff;
      overflow-y: auto;
      display: flex;
    }
    
    .sitecore-preview-search-items[data-loading='false'] {
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      list-style: none;
      margin: 0;
      padding: var(--sdc-spacing-s);
      gap: var(--sdc-spacing-m);
    }
    
    /* PreviewSearchRoot */
    .sitecore-preview-search-root {
    }
    
    /* PreviewSearchItem */
    .sitecore-preview-search-item {
    }
    
    /* ArticleRoot */
    .sitecore-article-root {
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      padding: var(--sdc-spacing-s);
      cursor: pointer;
      display: block;
      border: solid 1px transparent;
      text-align: center;
    }
    
    .sitecore-article-root:focus-within {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-root:hover {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    /* ArticleImage */
    .sitecore-article-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ArticleImageWrapper */
    .sitecore-article-image-wrapper {
      margin: auto auto 10px;
      position: relative;
      height: 6rem;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleName */
    .sitecore-article-name {
      max-height: 2rem;
      overflow: hidden;
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: 0.8rem;
      font-weight: var(--sdc-typography-fontSize4-fontWeight);
    }
    
    /* 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);
    }
    
    /* 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;
    }
    
    /* LoaderContainer */
    .sitecore-loader-container {
      align-items: center;
      display: flex;
      flex: 1;
    }
    
    /* LoaderAnimation */
    .sitecore-loader-animation {
      animation: rotate-animation 2s linear infinite;
      display: block;
      fill: var(--sdc-palette-primary-main);
      height: 50px;
      margin: auto;
      width: 50px;
    }
    
    /* SearchGroupHeading */
    .sitecore-search-group-heading {
      box-sizing: border-box;
      padding-left: var(--sdc-spacing-s);
    }

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 preview-search-left --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:

  • 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 { useCallback } from 'react';
    
    import { WidgetDataType, usePreviewSearch, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence, PreviewSearch } from '@sitecore-search/ui';
    
    import './styles.css';
    
    export const PreviewSearchLeftComponent = ({ defaultItemsPerPage = 6 }) => {
      const {
        widgetRef,
        actions: { onItemClick, onKeyphraseChange, onSuggestionClick },
        queryResult,
        queryResult: {
          isFetching,
          isLoading,
          data: {
            suggestion: {
              content_name_context_aware: articleSuggestions = [],
              content_trending_category: trendingCategorySuggestions = [],
            } = {},
          } = {},
        },
      } = usePreviewSearch({
        state: {
          suggestionsList: [
            {
              suggestion: 'content_name_context_aware',
              max: 10,
            },
            {
              suggestion: 'content_trending_category',
              max: 10,
            },
          ],
          itemsPerPage: defaultItemsPerPage,
        },
      });
      const loading = isLoading || isFetching;
      const keyphraseHandler = useCallback(
        (event) => {
          const target = event.target;
          onKeyphraseChange({
            keyphrase: target.value,
          });
        },
        [onKeyphraseChange],
      );
      return (
        <PreviewSearch.Root className="sitecore-preview-search-root">
          <PreviewSearch.Input
            onChange={keyphraseHandler}
            autoComplete="off"
            placeholder="Type to search..."
            className="sitecore-preview-search-input"
          />
          <PreviewSearch.Content ref={widgetRef} className="sitecore-preview-search-content">
            <Presence present={loading}>
              <div className="sitecore-loader-container">
                <svg
                  aria-busy={loading}
                  aria-hidden={!loading}
                  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>
              </div>
            </Presence>
            <Presence present={!loading}>
              <>
                {trendingCategorySuggestions.length + articleSuggestions.length > 0 && (
                  <PreviewSearch.Suggestions className="sitecore-preview-search-suggestions">
                    {trendingCategorySuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup
                        id="article_trending_category"
                        filterAttribute="category_name"
                        className="sitecore-preview-search-suggestions-group"
                      >
                        <h2 className="sitecore-search-group-heading">Trending Categories</h2>
                        {trendingCategorySuggestions.slice(0, 1).map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger
                            id={text}
                            key={text}
                            asChild
                            className="sitecore-preview-search-suggestion-trigger"
                          >
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_trending_category',
                                  title: 'Trending Categories',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                    {articleSuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup
                        id="article_name_context_aware"
                        className="sitecore-preview-search-suggestions-group"
                      >
                        <h2 className="sitecore-search-group-heading">Suggestions</h2>
                        {articleSuggestions.map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger
                            id={text}
                            key={text}
                            asChild
                            className="sitecore-preview-search-suggestion-trigger"
                          >
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_name_context_aware',
                                  title: 'Suggestions',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                  </PreviewSearch.Suggestions>
                )}
                <PreviewSearch.Results defaultQueryResult={queryResult}>
                  {({ isFetching: loading, data: { content: articles = [] } = {} }) => (
                    <PreviewSearch.Items data-loading={loading} className="sitecore-preview-search-items">
                      <Presence present={loading}>
                        <div className="sitecore-loader-container">
                          <svg
                            aria-busy={loading}
                            aria-hidden={!loading}
                            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>
                        </div>
                      </Presence>
                      {!loading &&
                        articles.map((article, index) => (
                          <PreviewSearch.Item key={article.id} asChild className="sitecore-preview-search-item">
                            <a
                              href={article.url}
                              onClick={(e) => {
                                e.preventDefault();
                                onItemClick({
                                  id: article.id,
                                  index,
                                  sourceId: article.source_id,
                                });
                                // add redirection or any action
                              }}
                              className="sitecore-preview-search-link"
                            >
                              <ArticleCard.Root className="sitecore-article-root">
                                <div className="sitecore-article-image-wrapper">
                                  <ArticleCard.Image src={article.image_url} className="sitecore-article-image" />
                                </div>
                                <ArticleCard.Title className="sitecore-article-name">{article.name}</ArticleCard.Title>
                              </ArticleCard.Root>
                            </a>
                          </PreviewSearch.Item>
                        ))}
                    </PreviewSearch.Items>
                  )}
                </PreviewSearch.Results>
              </>
            </Presence>
          </PreviewSearch.Content>
        </PreviewSearch.Root>
      );
    };
    const PreviewSearchLeftWidget = widget(PreviewSearchLeftComponent, WidgetDataType.PREVIEW_SEARCH, 'content');
    export default PreviewSearchLeftWidget;
    // styles.css
    @keyframes rotate-animation {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    
    /* PreviewSearchInput */
    .sitecore-preview-search-input {
      width: 800px;
      box-sizing: border-box;
      padding: var(--sdc-spacing-xs);
    }
    
    .sitecore-preview-search-input:focus {
      outline: 1px solid var(--sdc-palette-grey-400);
    }
    
    /* PreviewSearchContent */
    .sitecore-preview-search-content {
      animation-duration: 500ms;
      animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
      will-change: transform, opacity;
      width: var(--radix-popover-trigger-width);
      max-height: var(--radix-popover-content-available-height);
      background: var(--sdc-palette-primary-light);
      box-shadow: 2px 5px 5px 5px var(--sdc-palette-grey-400);
      display: flex;
      justify-content: center;
      height: 400px;
      padding-top: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
    }
    
    @keyframes slide-up-and-fade {
      from {
        opacity: 0;
        transform: translateY(2px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    @keyframes slide-right-and-fade {
      from {
        opacity: 0;
        transform: translateX(-2px);
      }
      to {
        opacity: 1;
        transform: translateX(0);
      }
    }
    
    @keyframes slide-down-and-fade {
      from {
        opacity: 0;
        transform: translateY(-2px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    @keyframes slide-left-and-fade {
      from {
        opacity: 0;
        transform: translateX(2px);
      }
      to {
        opacity: 1;
        transform: translateX(0);
      }
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='top'] {
      animation-name: slide-down-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='right'] {
      animation-name: slide-left-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='bottom'] {
      animation-name: slide-up-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='left'] {
      animation-name: slide-right-and-fade;
    }
    
    /* PreviewSearchSuggestions */
    .sitecore-preview-search-suggestions {
      display: block;
      box-sizing: border-box;
      list-style: none;
      width: 16rem;
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* PreviewSearchSuggestionItem */
    .sitecore-preview-search-suggestion-item {
      padding: var(--sdc-spacing-s) var(--sdc-spacing-s);
    }
    
    .sitecore-preview-search-suggestion-item:focus,
    .sitecore-preview-search-suggestion-item:hover {
      outline: none;
      font-weight: bold;
      color: var(--sdc-palette-primary-main);
      background: #fff;
    }
    
    /* PreviewSearchSuggestionTrigger */
    .sitecore-preview-search-suggestion-trigger {
      cursor: pointer;
      padding: var(--sdc-spacing-s) var(--sdc-spacing-s);
    }
    
    .sitecore-preview-search-suggestion-trigger[data-state='active'],
    .sitecore-preview-search-suggestion-trigger:focus,
    .sitecore-preview-search-suggestion-trigger:hover {
      outline: none;
      font-weight: bold;
      color: var(--sdc-palette-primary-main);
      background: #fff;
    }
    
    /* PreviewSearchSuggestionsGroup */
    .sitecore-preview-search-suggestions-group {
      flex: 1;
      display: flex;
      flex-direction: column;
    }
    
    /* PreviewSearchLink */
    .sitecore-preview-search-link {
      color: var(--sdc-palette-primary-main);
      display: flex;
      box-sizing: border-box;
      text-decoration: none;
      width: 100%;
    }
    
    .sitecore-preview-search-link:focus {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    /* PreviewSearchItems */
    .sitecore-preview-search-items {
      flex: 3;
      background: #fff;
      overflow-y: auto;
      display: flex;
    }
    
    .sitecore-preview-search-items[data-loading='false'] {
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      list-style: none;
      margin: 0;
      padding: var(--sdc-spacing-s);
      gap: var(--sdc-spacing-m);
    }
    
    /* PreviewSearchRoot */
    .sitecore-preview-search-root {
    }
    
    /* PreviewSearchItem */
    .sitecore-preview-search-item {
    }
    
    /* ArticleRoot */
    .sitecore-article-root {
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      padding: var(--sdc-spacing-s);
      cursor: pointer;
      display: block;
      border: solid 1px transparent;
      text-align: center;
    }
    
    .sitecore-article-root:focus-within {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-root:hover {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    /* ArticleImage */
    .sitecore-article-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ArticleImageWrapper */
    .sitecore-article-image-wrapper {
      margin: auto auto 10px;
      position: relative;
      height: 6rem;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleName */
    .sitecore-article-name {
      max-height: 2rem;
      overflow: hidden;
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: 0.8rem;
      font-weight: var(--sdc-typography-fontSize4-fontWeight);
    }
    
    /* 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);
    }
    
    /* 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;
    }
    
    /* LoaderContainer */
    .sitecore-loader-container {
      align-items: center;
      display: flex;
      flex: 1;
    }
    
    /* LoaderAnimation */
    .sitecore-loader-animation {
      animation: rotate-animation 2s linear infinite;
      display: block;
      fill: var(--sdc-palette-primary-main);
      height: 50px;
      margin: auto;
      width: 50px;
    }
    
    /* SearchGroupHeading */
    .sitecore-search-group-heading {
      box-sizing: border-box;
      padding-left: var(--sdc-spacing-s);
    }

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 preview-search-left --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 type { ChangeEvent } from 'react';
    import { useCallback } from 'react';
    
    import type { PreviewSearchInitialState } from '@sitecore-search/react';
    import { WidgetDataType, usePreviewSearch, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence, PreviewSearch } from '@sitecore-search/ui';
    
    import styles from './styles.module.css';
    
    type ArticleModel = {
      id: string;
      name: string;
      image_url: string;
      url: string;
      source_id?: string;
    };
    type InitialState = PreviewSearchInitialState<'itemsPerPage' | 'suggestionsList'>;
    export const PreviewSearchLeftComponent = ({ defaultItemsPerPage = 6 }) => {
      const {
        widgetRef,
        actions: { onItemClick, onKeyphraseChange, onSuggestionClick },
        queryResult,
        queryResult: {
          isFetching,
          isLoading,
          data: {
            suggestion: {
              content_name_context_aware: articleSuggestions = [],
              content_trending_category: trendingCategorySuggestions = [],
            } = {},
          } = {},
        },
      } = usePreviewSearch<ArticleModel, InitialState>({
        state: {
          suggestionsList: [
            {
              suggestion: 'content_name_context_aware',
              max: 10,
            },
            {
              suggestion: 'content_trending_category',
              max: 10,
            },
          ],
          itemsPerPage: defaultItemsPerPage,
        },
      });
      const loading = isLoading || isFetching;
      const keyphraseHandler = useCallback(
        (event: ChangeEvent<HTMLInputElement>) => {
          const target = event.target;
          onKeyphraseChange({
            keyphrase: target.value,
          });
        },
        [onKeyphraseChange],
      );
      return (
        <PreviewSearch.Root className={styles['sitecore-preview-search-root']}>
          <PreviewSearch.Input
            onChange={keyphraseHandler}
            autoComplete="off"
            placeholder="Type to search..."
            className={styles['sitecore-preview-search-input']}
          />
          <PreviewSearch.Content ref={widgetRef} className={styles['sitecore-preview-search-content']}>
            <Presence present={loading}>
              <div className={styles['sitecore-loader-container']}>
                <svg
                  aria-busy={loading}
                  aria-hidden={!loading}
                  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>
              </div>
            </Presence>
            <Presence present={!loading}>
              <>
                {trendingCategorySuggestions.length + articleSuggestions.length > 0 && (
                  <PreviewSearch.Suggestions className={styles['sitecore-preview-search-suggestions']}>
                    {trendingCategorySuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup
                        id="article_trending_category"
                        filterAttribute="category_name"
                        className={styles['sitecore-preview-search-suggestions-group']}
                      >
                        <h2 className={styles['sitecore-search-group-heading']}>Trending Categories</h2>
                        {trendingCategorySuggestions.slice(0, 1).map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger
                            id={text}
                            key={text}
                            asChild
                            className={styles['sitecore-preview-search-suggestion-trigger']}
                          >
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_trending_category',
                                  title: 'Trending Categories',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                    {articleSuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup
                        id="article_name_context_aware"
                        className={styles['sitecore-preview-search-suggestions-group']}
                      >
                        <h2 className={styles['sitecore-search-group-heading']}>Suggestions</h2>
                        {articleSuggestions.map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger
                            id={text}
                            key={text}
                            asChild
                            className={styles['sitecore-preview-search-suggestion-trigger']}
                          >
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_name_context_aware',
                                  title: 'Suggestions',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                  </PreviewSearch.Suggestions>
                )}
                <PreviewSearch.Results defaultQueryResult={queryResult}>
                  {({ isFetching: loading, data: { content: articles = [] } = {} }) => (
                    <PreviewSearch.Items data-loading={loading} className={styles['sitecore-preview-search-items']}>
                      <Presence present={loading}>
                        <div className={styles['sitecore-loader-container']}>
                          <svg
                            aria-busy={loading}
                            aria-hidden={!loading}
                            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>
                        </div>
                      </Presence>
                      {!loading &&
                        articles.map((article, index) => (
                          <PreviewSearch.Item key={article.id} asChild className={styles['sitecore-preview-search-item']}>
                            <a
                              href={article.url}
                              onClick={(e) => {
                                e.preventDefault();
                                onItemClick({
                                  id: article.id,
                                  index,
                                  sourceId: article.source_id,
                                });
                                // add redirection or any action
                              }}
                              className={styles['sitecore-preview-search-link']}
                            >
                              <ArticleCard.Root className={styles['sitecore-article-root']}>
                                <div className={styles['sitecore-article-image-wrapper']}>
                                  <ArticleCard.Image src={article.image_url} className={styles['sitecore-article-image']} />
                                </div>
                                <ArticleCard.Title className={styles['sitecore-article-name']}>
                                  {article.name}
                                </ArticleCard.Title>
                              </ArticleCard.Root>
                            </a>
                          </PreviewSearch.Item>
                        ))}
                    </PreviewSearch.Items>
                  )}
                </PreviewSearch.Results>
              </>
            </Presence>
          </PreviewSearch.Content>
        </PreviewSearch.Root>
      );
    };
    const PreviewSearchLeftWidget = widget(PreviewSearchLeftComponent, WidgetDataType.PREVIEW_SEARCH, 'content');
    export default PreviewSearchLeftWidget;
    // styles.module.css
    @keyframes rotate-animation {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    
    /* PreviewSearchInput */
    .sitecore-preview-search-input {
      width: 800px;
      box-sizing: border-box;
      padding: var(--sdc-spacing-xs);
    }
    
    .sitecore-preview-search-input:focus {
      outline: 1px solid var(--sdc-palette-grey-400);
    }
    
    /* PreviewSearchContent */
    .sitecore-preview-search-content {
      animation-duration: 500ms;
      animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
      will-change: transform, opacity;
      width: var(--radix-popover-trigger-width);
      max-height: var(--radix-popover-content-available-height);
      background: var(--sdc-palette-primary-light);
      box-shadow: 2px 5px 5px 5px var(--sdc-palette-grey-400);
      display: flex;
      justify-content: center;
      height: 400px;
      padding-top: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
    }
    
    @keyframes slide-up-and-fade {
      from {
        opacity: 0;
        transform: translateY(2px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    @keyframes slide-right-and-fade {
      from {
        opacity: 0;
        transform: translateX(-2px);
      }
      to {
        opacity: 1;
        transform: translateX(0);
      }
    }
    
    @keyframes slide-down-and-fade {
      from {
        opacity: 0;
        transform: translateY(-2px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    @keyframes slide-left-and-fade {
      from {
        opacity: 0;
        transform: translateX(2px);
      }
      to {
        opacity: 1;
        transform: translateX(0);
      }
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='top'] {
      animation-name: slide-down-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='right'] {
      animation-name: slide-left-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='bottom'] {
      animation-name: slide-up-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='left'] {
      animation-name: slide-right-and-fade;
    }
    
    /* PreviewSearchSuggestions */
    .sitecore-preview-search-suggestions {
      display: block;
      box-sizing: border-box;
      list-style: none;
      width: 16rem;
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* PreviewSearchSuggestionItem */
    .sitecore-preview-search-suggestion-item {
      padding: var(--sdc-spacing-s) var(--sdc-spacing-s);
    }
    
    .sitecore-preview-search-suggestion-item:focus,
    .sitecore-preview-search-suggestion-item:hover {
      outline: none;
      font-weight: bold;
      color: var(--sdc-palette-primary-main);
      background: #fff;
    }
    
    /* PreviewSearchSuggestionTrigger */
    .sitecore-preview-search-suggestion-trigger {
      cursor: pointer;
      padding: var(--sdc-spacing-s) var(--sdc-spacing-s);
    }
    
    .sitecore-preview-search-suggestion-trigger[data-state='active'],
    .sitecore-preview-search-suggestion-trigger:focus,
    .sitecore-preview-search-suggestion-trigger:hover {
      outline: none;
      font-weight: bold;
      color: var(--sdc-palette-primary-main);
      background: #fff;
    }
    
    /* PreviewSearchSuggestionsGroup */
    .sitecore-preview-search-suggestions-group {
      flex: 1;
      display: flex;
      flex-direction: column;
    }
    
    /* PreviewSearchLink */
    .sitecore-preview-search-link {
      color: var(--sdc-palette-primary-main);
      display: flex;
      box-sizing: border-box;
      text-decoration: none;
      width: 100%;
    }
    
    .sitecore-preview-search-link:focus {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    /* PreviewSearchItems */
    .sitecore-preview-search-items {
      flex: 3;
      background: #fff;
      overflow-y: auto;
      display: flex;
    }
    
    .sitecore-preview-search-items[data-loading='false'] {
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      list-style: none;
      margin: 0;
      padding: var(--sdc-spacing-s);
      gap: var(--sdc-spacing-m);
    }
    
    /* PreviewSearchRoot */
    .sitecore-preview-search-root {
    }
    
    /* PreviewSearchItem */
    .sitecore-preview-search-item {
    }
    
    /* ArticleRoot */
    .sitecore-article-root {
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      padding: var(--sdc-spacing-s);
      cursor: pointer;
      display: block;
      border: solid 1px transparent;
      text-align: center;
    }
    
    .sitecore-article-root:focus-within {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-root:hover {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    /* ArticleImage */
    .sitecore-article-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ArticleImageWrapper */
    .sitecore-article-image-wrapper {
      margin: auto auto 10px;
      position: relative;
      height: 6rem;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleName */
    .sitecore-article-name {
      max-height: 2rem;
      overflow: hidden;
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: 0.8rem;
      font-weight: var(--sdc-typography-fontSize4-fontWeight);
    }
    
    /* 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);
    }
    
    /* 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;
    }
    
    /* LoaderContainer */
    .sitecore-loader-container {
      align-items: center;
      display: flex;
      flex: 1;
    }
    
    /* LoaderAnimation */
    .sitecore-loader-animation {
      animation: rotate-animation 2s linear infinite;
      display: block;
      fill: var(--sdc-palette-primary-main);
      height: 50px;
      margin: auto;
      width: 50px;
    }
    
    /* SearchGroupHeading */
    .sitecore-search-group-heading {
      box-sizing: border-box;
      padding-left: var(--sdc-spacing-s);
    }

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 preview-search-left --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 { useCallback } from 'react';
    
    import { WidgetDataType, usePreviewSearch, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence, PreviewSearch } from '@sitecore-search/ui';
    
    import styles from './styles.module.css';
    
    export const PreviewSearchLeftComponent = ({ defaultItemsPerPage = 6 }) => {
      const {
        widgetRef,
        actions: { onItemClick, onKeyphraseChange, onSuggestionClick },
        queryResult,
        queryResult: {
          isFetching,
          isLoading,
          data: {
            suggestion: {
              content_name_context_aware: articleSuggestions = [],
              content_trending_category: trendingCategorySuggestions = [],
            } = {},
          } = {},
        },
      } = usePreviewSearch({
        state: {
          suggestionsList: [
            {
              suggestion: 'content_name_context_aware',
              max: 10,
            },
            {
              suggestion: 'content_trending_category',
              max: 10,
            },
          ],
          itemsPerPage: defaultItemsPerPage,
        },
      });
      const loading = isLoading || isFetching;
      const keyphraseHandler = useCallback(
        (event) => {
          const target = event.target;
          onKeyphraseChange({
            keyphrase: target.value,
          });
        },
        [onKeyphraseChange],
      );
      return (
        <PreviewSearch.Root className={styles['sitecore-preview-search-root']}>
          <PreviewSearch.Input
            onChange={keyphraseHandler}
            autoComplete="off"
            placeholder="Type to search..."
            className={styles['sitecore-preview-search-input']}
          />
          <PreviewSearch.Content ref={widgetRef} className={styles['sitecore-preview-search-content']}>
            <Presence present={loading}>
              <div className={styles['sitecore-loader-container']}>
                <svg
                  aria-busy={loading}
                  aria-hidden={!loading}
                  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>
              </div>
            </Presence>
            <Presence present={!loading}>
              <>
                {trendingCategorySuggestions.length + articleSuggestions.length > 0 && (
                  <PreviewSearch.Suggestions className={styles['sitecore-preview-search-suggestions']}>
                    {trendingCategorySuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup
                        id="article_trending_category"
                        filterAttribute="category_name"
                        className={styles['sitecore-preview-search-suggestions-group']}
                      >
                        <h2 className={styles['sitecore-search-group-heading']}>Trending Categories</h2>
                        {trendingCategorySuggestions.slice(0, 1).map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger
                            id={text}
                            key={text}
                            asChild
                            className={styles['sitecore-preview-search-suggestion-trigger']}
                          >
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_trending_category',
                                  title: 'Trending Categories',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                    {articleSuggestions.length > 0 && (
                      <PreviewSearch.SuggestionsGroup
                        id="article_name_context_aware"
                        className={styles['sitecore-preview-search-suggestions-group']}
                      >
                        <h2 className={styles['sitecore-search-group-heading']}>Suggestions</h2>
                        {articleSuggestions.map(({ text }) => (
                          <PreviewSearch.SuggestionTrigger
                            id={text}
                            key={text}
                            asChild
                            className={styles['sitecore-preview-search-suggestion-trigger']}
                          >
                            <a
                              onClick={() => {
                                onSuggestionClick({
                                  name: 'article_name_context_aware',
                                  title: 'Suggestions',
                                  value: text,
                                  displayName: text,
                                });
                              }}
                            >
                              {text}
                            </a>
                          </PreviewSearch.SuggestionTrigger>
                        ))}
                      </PreviewSearch.SuggestionsGroup>
                    )}
                  </PreviewSearch.Suggestions>
                )}
                <PreviewSearch.Results defaultQueryResult={queryResult}>
                  {({ isFetching: loading, data: { content: articles = [] } = {} }) => (
                    <PreviewSearch.Items data-loading={loading} className={styles['sitecore-preview-search-items']}>
                      <Presence present={loading}>
                        <div className={styles['sitecore-loader-container']}>
                          <svg
                            aria-busy={loading}
                            aria-hidden={!loading}
                            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>
                        </div>
                      </Presence>
                      {!loading &&
                        articles.map((article, index) => (
                          <PreviewSearch.Item key={article.id} asChild className={styles['sitecore-preview-search-item']}>
                            <a
                              href={article.url}
                              onClick={(e) => {
                                e.preventDefault();
                                onItemClick({
                                  id: article.id,
                                  index,
                                  sourceId: article.source_id,
                                });
                                // add redirection or any action
                              }}
                              className={styles['sitecore-preview-search-link']}
                            >
                              <ArticleCard.Root className={styles['sitecore-article-root']}>
                                <div className={styles['sitecore-article-image-wrapper']}>
                                  <ArticleCard.Image src={article.image_url} className={styles['sitecore-article-image']} />
                                </div>
                                <ArticleCard.Title className={styles['sitecore-article-name']}>
                                  {article.name}
                                </ArticleCard.Title>
                              </ArticleCard.Root>
                            </a>
                          </PreviewSearch.Item>
                        ))}
                    </PreviewSearch.Items>
                  )}
                </PreviewSearch.Results>
              </>
            </Presence>
          </PreviewSearch.Content>
        </PreviewSearch.Root>
      );
    };
    const PreviewSearchLeftWidget = widget(PreviewSearchLeftComponent, WidgetDataType.PREVIEW_SEARCH, 'content');
    export default PreviewSearchLeftWidget;
    // styles.module.css
    @keyframes rotate-animation {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    
    /* PreviewSearchInput */
    .sitecore-preview-search-input {
      width: 800px;
      box-sizing: border-box;
      padding: var(--sdc-spacing-xs);
    }
    
    .sitecore-preview-search-input:focus {
      outline: 1px solid var(--sdc-palette-grey-400);
    }
    
    /* PreviewSearchContent */
    .sitecore-preview-search-content {
      animation-duration: 500ms;
      animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
      will-change: transform, opacity;
      width: var(--radix-popover-trigger-width);
      max-height: var(--radix-popover-content-available-height);
      background: var(--sdc-palette-primary-light);
      box-shadow: 2px 5px 5px 5px var(--sdc-palette-grey-400);
      display: flex;
      justify-content: center;
      height: 400px;
      padding-top: 0;
      font-family: var(--sdc-typography-fontFamilySystem);
    }
    
    @keyframes slide-up-and-fade {
      from {
        opacity: 0;
        transform: translateY(2px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    @keyframes slide-right-and-fade {
      from {
        opacity: 0;
        transform: translateX(-2px);
      }
      to {
        opacity: 1;
        transform: translateX(0);
      }
    }
    
    @keyframes slide-down-and-fade {
      from {
        opacity: 0;
        transform: translateY(-2px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    @keyframes slide-left-and-fade {
      from {
        opacity: 0;
        transform: translateX(2px);
      }
      to {
        opacity: 1;
        transform: translateX(0);
      }
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='top'] {
      animation-name: slide-down-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='right'] {
      animation-name: slide-left-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='bottom'] {
      animation-name: slide-up-and-fade;
    }
    
    .sitecore-preview-search-content[data-state='open'][data-side='left'] {
      animation-name: slide-right-and-fade;
    }
    
    /* PreviewSearchSuggestions */
    .sitecore-preview-search-suggestions {
      display: block;
      box-sizing: border-box;
      list-style: none;
      width: 16rem;
      font-size: var(--sdc-typography-fontSize1-fontSize);
    }
    
    /* PreviewSearchSuggestionItem */
    .sitecore-preview-search-suggestion-item {
      padding: var(--sdc-spacing-s) var(--sdc-spacing-s);
    }
    
    .sitecore-preview-search-suggestion-item:focus,
    .sitecore-preview-search-suggestion-item:hover {
      outline: none;
      font-weight: bold;
      color: var(--sdc-palette-primary-main);
      background: #fff;
    }
    
    /* PreviewSearchSuggestionTrigger */
    .sitecore-preview-search-suggestion-trigger {
      cursor: pointer;
      padding: var(--sdc-spacing-s) var(--sdc-spacing-s);
    }
    
    .sitecore-preview-search-suggestion-trigger[data-state='active'],
    .sitecore-preview-search-suggestion-trigger:focus,
    .sitecore-preview-search-suggestion-trigger:hover {
      outline: none;
      font-weight: bold;
      color: var(--sdc-palette-primary-main);
      background: #fff;
    }
    
    /* PreviewSearchSuggestionsGroup */
    .sitecore-preview-search-suggestions-group {
      flex: 1;
      display: flex;
      flex-direction: column;
    }
    
    /* PreviewSearchLink */
    .sitecore-preview-search-link {
      color: var(--sdc-palette-primary-main);
      display: flex;
      box-sizing: border-box;
      text-decoration: none;
      width: 100%;
    }
    
    .sitecore-preview-search-link:focus {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    /* PreviewSearchItems */
    .sitecore-preview-search-items {
      flex: 3;
      background: #fff;
      overflow-y: auto;
      display: flex;
    }
    
    .sitecore-preview-search-items[data-loading='false'] {
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      list-style: none;
      margin: 0;
      padding: var(--sdc-spacing-s);
      gap: var(--sdc-spacing-m);
    }
    
    /* PreviewSearchRoot */
    .sitecore-preview-search-root {
    }
    
    /* PreviewSearchItem */
    .sitecore-preview-search-item {
    }
    
    /* ArticleRoot */
    .sitecore-article-root {
      box-shadow: 2px 2px 4px var(--sdc-palette-grey-400);
      padding: var(--sdc-spacing-s);
      cursor: pointer;
      display: block;
      border: solid 1px transparent;
      text-align: center;
    }
    
    .sitecore-article-root:focus-within {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    .sitecore-article-root:hover {
      box-shadow: 2px 2px 4px var(--sdc-palette-primary-main);
    }
    
    /* ArticleImage */
    .sitecore-article-image {
      display: block;
      width: auto;
      max-width: 100%;
      height: auto;
      max-height: 100%;
    }
    
    /* ArticleImageWrapper */
    .sitecore-article-image-wrapper {
      margin: auto auto 10px;
      position: relative;
      height: 6rem;
      display: flex;
      justify-content: center;
      align-items: center;
      overflow: hidden;
    }
    
    /* ArticleName */
    .sitecore-article-name {
      max-height: 2rem;
      overflow: hidden;
      margin: 0 0 var(--sdc-spacing-m);
      font-family: var(--sdc-typography-fontFamilySystem);
      font-size: 0.8rem;
      font-weight: var(--sdc-typography-fontSize4-fontWeight);
    }
    
    /* 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);
    }
    
    /* 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;
    }
    
    /* LoaderContainer */
    .sitecore-loader-container {
      align-items: center;
      display: flex;
      flex: 1;
    }
    
    /* LoaderAnimation */
    .sitecore-loader-animation {
      animation: rotate-animation 2s linear infinite;
      display: block;
      fill: var(--sdc-palette-primary-main);
      height: 50px;
      margin: auto;
      width: 50px;
    }
    
    /* SearchGroupHeading */
    .sitecore-search-group-heading {
      box-sizing: border-box;
      padding-left: var(--sdc-spacing-s);
    }

Options

Items per page

defaultItemsPerPage sets the maximum number of items displayed in this component.

Default: 6

<PreviewSearchLeft
	rfkId="rfkid_6"
 	defaultItemsPerPage={12}
/>
If you have suggestions for improving this article, let us know!