# Suggestions

**Category:** Features/Display/Suggestions

## Design

### Description

Displays marketing offers from the Wix Dealer service. For more information, see [Suggestions Overview](./?path=/story/features-display-suggestions--overview).

```tsx
import { Suggestions, renderMarketingCard } from '@wix/patterns';
```

---

### Variations

### Basic Usage

Use `renderMarketingCard()` when your offer's `asset.creative` matches the `MarketingCardSchema` structure.

```tsx
/* eslint-disable */
import React from 'react';
import { Suggestions, renderMarketingCard } from '@wix/patterns';

function Basic() {
  return (
    <Suggestions
      placementId="your-placement-id"
      renderCard={renderMarketingCard()}
    />
  );
}

export default Basic;
```

---

### With Transform Function

Use `renderMarketingCard(transformFn)` when your offer data has a different structure and needs to be transformed into the `MarketingCardSchema` format.

```tsx
/* eslint-disable */
import React from 'react';
import {
  Suggestions,
  renderMarketingCard,
  MarketingCardSchema,
  type Offer,
} from '@wix/patterns';

function TransformOffer() {
    // Transform offers with a different structure into MarketingCardSchema
const transformOffer = (offer: Offer): MarketingCardSchema | null => {
    const creative = offer.asset?.creative;
  
    // if no creative, filter out the offer
    if (!creative) {
      return null;
    }

    // The offer has: headline, body, media.url, action.label, action.url, tag
    // We need to transform it to: title, description.text, imageUrl, ctaButton, badge
    return {
      title: creative.headline,
      description: {
        text: creative.body,
        learnMore: creative.helpLink
          ? {
              link: creative.helpLink,
              text: 'Learn more',
            }
          : undefined,
      },
      imageUrl: creative.media?.url,
      ctaButton: creative.action
        ? {
            text: creative.action.label,
            link: creative.action.url,
          }
        : undefined,
      badge: creative.tag
        ? {
            text: creative.tag,
            skin: 'general',
          }
        : undefined,
    };
  };
  return (
    <Suggestions
      placementId="custom-structure-placement-id"
      renderCard={renderMarketingCard(transformOffer)}
    />
  );
}

export default TransformOffer;
```

---

### Custom Card Rendering

Provide a custom `renderCard` function for complete control over the card's appearance and behavior.

```tsx
/* eslint-disable */
import React from 'react';
import { Suggestions, SuggestionsCardProps } from '@wix/patterns';
import { Text, Box, TextButton, Card } from '@wix/design-system';
import { ArrowRight } from '@wix/wix-ui-icons-common';

function CustomCard() {

const GRADIENTS = [
    'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
    'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
    'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
    'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
  ];
  // renderCard receives: offer (the dealer offer), index (0-based position), totalOffers (count of all offers)
  const renderCustomCard = ({ offer, index, totalOffers }: SuggestionsCardProps) => {
    const creative = offer.asset?.creative;

    if (!creative) {
      return null;
    }

    const gradient = GRADIENTS[index % GRADIENTS.length];

    return (
      <Box width="350px">
        <Card stretchVertically>
          <Card.Content>
            <Box direction="vertical" gap="12px" padding="12px">
              <Box align="space-between" verticalAlign="middle">
                <Box
                  align="center"
                  verticalAlign="middle"
                  width="36px"
                  height="36px"
                  borderRadius="50%"
                  backgroundColor="D80"
                >
                  <Text size="small" weight="bold">
                    {String(index + 1).padStart(2, '0')}
                  </Text>
                </Box>
                {creative.badge && (
                  <Box
                    padding="4px 12px"
                    borderRadius="20px"
                    style={{ background: gradient }}
                  >
                    <Text size="tiny" weight="bold" style={{ color: 'white' }}>
                      {creative.badge.text}
                    </Text>
                  </Box>
                )}
              </Box>

              <Box direction="vertical" gap="6px">
                <Text size="medium" weight="bold" maxLines={2}>
                  {creative.title}
                </Text>
                <Box
                  height="3px"
                  width="40px"
                  borderRadius="2px"
                  style={{ background: gradient }}
                />
              </Box>

              <Text size="small" secondary maxLines={2}>
                {creative.description?.text}
              </Text>

              {creative.ctaButton && (
                <Box marginTop="12px">
                  <TextButton
                    size="small"
                    as="a"
                    href={creative.ctaButton.link}
                    suffixIcon={<ArrowRight />}
                  >
                    {creative.ctaButton.text}
                  </TextButton>
                </Box>
              )}
            </Box>
          </Card.Content>
        </Card>
      </Box>
    );
  };

  return (
    <Suggestions
      placementId="your-placement-id"
      renderCard={renderCustomCard}
    />
  );
}

export default CustomCard;
```

---

### Carousel vs Container Layout

When the number of offers exceeds `maxInlineItems`, the component switches from a fixed container layout (Box) to carousel layout. This example shows the same 3 offers rendered with different `maxInlineItems` values.

```tsx
/* eslint-disable */
import React from 'react';
import { Suggestions, renderMarketingCard } from '@wix/patterns';
import { Box, Heading, Text } from '@wix/design-system';

function CarouselLayout() {
  return (
    <Box direction="vertical" gap="SP6">
      <Box direction="vertical" gap="SP2">
        <Box direction="vertical" gap="SP1">
          <Heading size="small">Inline Layout</Heading>
          <Text size="small" secondary>
            3 offers with maxInlineItems=3 → Cards displayed side by side
          </Text>
        </Box>
        <Suggestions
          placementId="3-offers-placement-id"
          renderCard={renderMarketingCard()}
          maxInlineItems={3}
        />
      </Box>

      <Box direction="vertical" gap="SP2">
        <Box direction="vertical" gap="SP1">
          <Heading size="small">Carousel Layout</Heading>
          <Text size="small" secondary>
            3 offers with maxInlineItems=2 → Carousel with navigation
          </Text>
        </Box>
        <Suggestions
          placementId="3-offers-placement-id"
          renderCard={renderMarketingCard()}
          maxInlineItems={2}
        />
      </Box>
    </Box>
  );
}

export default CarouselLayout;
```

---

### Carousel Customization

Use `carouselProps` to customize the carousel behavior. See the full [Carousel documentation](https://www.wix-pages.com/wix-design-system-employees/?path=/story/components-media--carousel) for all available options.

```tsx
/* eslint-disable */
import React from 'react';
import { Suggestions, renderMarketingCard } from '@wix/patterns';

function CarouselProps() {
  return (
    <Suggestions
      placementId="your-placement-id"
      renderCard={renderMarketingCard()}
      maxInlineItems={1}
      carouselProps={{
        // Enable infinite scrolling
        infinite: true,
        // Show navigation dots
        dots: true,
        // Position controls outside the carousel
        controlsPosition: 'sides',
        // Hide shadow on controls
        showControlsShadow: false,
        // Allow variable width cards
        variableWidth: true,
      }}
    />
  );
}

export default CarouselProps;
```

## API

### API

For information about the `MarketingCardSchema` structure used with `renderMarketingCard`, see [Suggestions Overview](./?path=/story/features-display-suggestions--overview#marketingcardschema).

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `placementId` | `string` | Yes | - | The placement ID to fetch offers for. |
| `carouselProps` | `CarouselProps` | No | - | Props to pass to the carousel component. |
| `renderCard` | `(props: SuggestionsCardProps) => ReactNode` | Yes | - | Render function for each offer card. Return `null` to filter out a card. |
| `CardSkeleton` | `ComponentType<BaseCardComponentProps>` | No | - | Skeleton component to render when loading. |
| `skeletonsNumber` | `number` | No | `4` | Number of skeletons to render when loading. |
| `dataHook` | `string` | No | `'suggestions'` | Data hook for testing. |
| `maxInlineItems` | `number` | No | `2` | Maximum number of items to display inline (Box layout). Above this, Carousel is used. |

## Testkit

### Usage

## Import

```typescript
// Testkit (for testing frameworks)
import { SuggestionsTestkit } from '@wix/patterns/testkit/jsdom';
import { SuggestionsTestkit } from '@wix/patterns/testkit/puppeteer';
import { SuggestionsTestkit } from '@wix/patterns/testkit/enzyme';
import { SuggestionsTestkit } from '@wix/patterns/testkit/playwright';

// UniDriver (for direct driver usage)
import { SuggestionsUniDriver } from '@wix/patterns/testkit/unidriver';
```

### API

| Method | Return Type | Description |
|--------|-------------|-------------|
| `exists()` | `Promise` | Returns true if the suggestions component exists in the DOM |
| `isLoading()` | `Promise` | Returns true if the component is in loading state |
| `hasOffers()` | `Promise` | Returns true if offers are displayed |
| `isEmpty()` | `Promise` | Returns true if no offers or loading state are present |
| `getCardsCount()` | `Promise` | Returns the number of cards currently displayed |
| `getCardAt(index)` | `Promise` | Returns the card element at the specified index |
| `carousel()` | `Promise` | Returns the carousel driver (when in carousel mode) |
| `container()` | `Promise` | Returns the container element (when in inline mode) |

---

## MarketingCard Driver

The `MarketingCard` component (the default card used by `Suggestions`) also exposes a testkit driver for interacting with individual cards in tests.

### Import

```typescript
import { MarketingCardUniDriver } from '@wix/patterns/testkit/unidriver';
```

### Driver Methods

| Method | Return Type | Description |
|--------|-------------|-------------|
| `exists()` | `Promise` | Returns true if the card exists in the DOM |
| `getTitleText()` | `Promise` | Returns the card's title text |
| `description.subtitle` | `TextUniDriver` | Driver for the card's subtitle text |
| `description.learnMore` | `TextButtonUniDriver` | Driver for the "Learn More" text button |
| `ctaButton` | `ButtonUniDriver` | Driver for the card's CTA button |
| `badge` | `BadgeUniDriver` | Driver for the card's badge |
| `image` | `ImageUniDriver` | Driver for the card's image |


