Upsells are banners that display short messages that focus on promoting an action or upgrading something the user already has.

also known as Banner, Offer Banner, CTA Banner

Figma:

Responsive:

Adaptive:

A11y:

Props

Component props
Name
Type
Default
message
Required
string | React.Element<typeof Text>
-

Main content of Upsell, explains what is being offered or recommended. Content should be localized. See the Message variant to learn more.

children
React.Element<typeof UpsellForm>
-

To create forms within Upsell, pass Upsell.Form as children.

dismissButton
{|
  accessibilityLabel?: string,
  onDismiss: () => void,
|}
-

Adds a dismiss button to the Upsell. The `accessibilityLabel` should follow the Accessibility guidelines.

imageData
{|
  component: React.Element<typeof Image | typeof Icon>,
  mask?: {|
    rounding?: "circle" | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8,
    wash?: boolean,
  |},
  width?: number,
|}
-

Either an Icon or an Image to render at the start of the banner. Width is not used with Icon. Image width defaults to 128px. See the Icon and Image variants for more info.

primaryAction
{|
    accessibilityLabel: string,
    disabled?: boolean,
    href: string,
    label: string,
    onClick?: $ElementType<React$ElementConfig<typeof ButtonLink>, "onClick">,
    rel?: "none" | "nofollow",
    role: "link",
    target?: null | "self" | "blank",
  |}
| {|
    accessibilityLabel: string,
    disabled?: boolean,
    label: string,
    onClick: $ElementType<React$ElementConfig<typeof Button>, "onClick">,
    role?: "button",
  |}
-

Main action for people to take on Upsell. If `href` is supplied, the action will serve as a link. See GlobalEventsHandlerProvider to learn more about link navigation.'
If no `href` is supplied, the action will be a button.
The `accessibilityLabel` should follow the Accessibility guidelines.

secondaryAction
{|
    accessibilityLabel: string,
    disabled?: boolean,
    href: string,
    label: string,
    onClick?: $ElementType<React$ElementConfig<typeof ButtonLink>, "onClick">,
    rel?: "none" | "nofollow",
    role: "link",
    target?: null | "self" | "blank",
  |}
| {|
    accessibilityLabel: string,
    disabled?: boolean,
    label: string,
    onClick: $ElementType<React$ElementConfig<typeof Button>, "onClick">,
    role?: "button",
  |}
-

Secondary action for people to take on Upsell. If `href` is supplied, the action will serve as a link. See GlobalEventsHandlerProvider to learn more about link navigation.'
If no `href` is supplied, the action will be a button.
The `accessibilityLabel` should follow the Accessibility guidelines.

title
string
-

Brief title summarizing the Upsell. Content should be localized.

Subcomponents

Upsell.Form

Upsell.Form can be used to add a short form to Upsell for collecting data from the user.

Upsell.Form Props

Upsell.Form subcomponent props
Name
Type
Default
children
Required
React.Node
-

Contents of the form, typically input components like TextField or NumberField.

onSubmit
Required
({|
  event:
    | SyntheticMouseEvent<HTMLButtonElement>
    | SyntheticMouseEvent<HTMLAnchorElement>
    | SyntheticKeyboardEvent<HTMLAnchorElement>
    | SyntheticKeyboardEvent<HTMLButtonElement>,
|}) => void
-

Callback triggered when the form is submitted.

submitButtonAccessibilityLabel
Required
string
-

Label for the submit button used for screen readers. Should follow the Accessibility guidelines. Be sure to localize!

submitButtonText
Required
string
-

Text content of the submit button. Be sure to localize!

submitButtonDisabled
boolean
-

Used to disable the submit button.

Usage guidelines

When to use
  • Displaying promotional information to a user that is not tied to a task or state on the surface.
  • Sharing updates or changes to the features and offerings of the product.
When not to use
  • Anything related to state or status within the surface. Consider a Callout instead.
  • Promoting or highlighting specific elements / areas within a surface. Let the team know if this is needed.

Best practices

Do

Use Upsells for marketing new products or encouraging upgrades.

Do

Place Upsell at the top of the page under the primary navigation when possible.

Do

Plan for the timing of your Upsells with new product launches. Try to create different messages for each time an Upsell appears to the user.

Don't

Use Upsells for critical information, such as errors or warnings. Use Callout instead. Upsells should not be used for general information either.

Don't

Stack Upsells on a page. In the case that they must be stacked, Callouts will appear above Upsells.

Don't

Keep showing the same Upsell once it has been dismissed. Upsells should only appear a maximum of 2 times to the same user, as they have diminishing returns.

Accessibility

Labels

dismissButton, primaryAction, secondaryAction, and submitButtonAccessibilityLabel each require a short, descriptive label for screen readers, which should also be localized.

In the case of action Buttons or Links, alternative text should be provided through the accessibilityLabel prop to replace vague text like "Visit" or "Learn more" with more descriptive information, like "Learn more about work from home resources". Avoid using the words "button" or "link" in the label, as this becomes repetitive. If the action text is already descriptive, an empty string can be passed.

For the dismissButton IconButton, the label provided should indicate the intent, like “Dismiss this banner”.

The Image or Icon supplied to imageData should only supply an alt or accessibilityLabel, respectively, if the Image or Icon supplies extra context or information. Icons in Upsells are often purely decorative, and can therefore have an empty string as the accessibilityLabel.

import { Box, Icon, Upsell } from 'gestalt';

export default function Example() {
  return (
    <Box
      padding={8}
      height="100%"
      display="flex"
      alignItems="center"
      justifyContent="center"
    >
      <Upsell
        dismissButton={{
          accessibilityLabel: 'Dismiss banner',
          onDismiss: () => {},
        }}
        imageData={{
          component: (
            <Icon
              icon="pinterest"
              accessibilityLabel=""
              color="default"
              size={32}
            />
          ),
        }}
        message="Earn $60 of ads credit, and give $30 of ads credit to a friend"
        primaryAction={{
          href: 'https://pinterest.com',
          label: 'Send invite',
          accessibilityLabel: 'Invite friend to use ads',
          target: 'blank',
          role: 'link',
        }}
        secondaryAction={{
          accessibilityLabel: 'Learn more: Verified Merchant Program',
          href: 'https://help.pinterest.com/en/business/article/verified-merchant-program',
          label: 'Learn more',
          target: 'blank',
          role: 'link',
        }}
        title="Give $30, get $60 in ads credit"
      />
    </Box>
  );
}

Localization

Remember to localize all link or button labels, as well as title and message.

import { Box, Icon, Upsell } from 'gestalt';

export default function Example() {
  return (
    <Box
      padding={8}
      height="100%"
      display="flex"
      alignItems="center"
      justifyContent="center"
    >
      <Upsell
        imageData={{
          component: (
            <Icon icon="send" accessibilityLabel="" color="default" size={32} />
          ),
        }}
        message="Verfolgen Sie die Anzeigenkonvertierung - Umsatz, Traffic und mehr - mit dem Pinterest Tag"
        primaryAction={{
          label: 'Beanspruche jetzt',
          accessibilityLabel: 'Beanspruche Guthaben jetzt',
          role: 'button',
          onClick: () => {},
        }}
        title="Fast fertig! Beenden Sie die Installation Ihres Pinterest-Tags und erhalten Sie ein Guthaben von 10 Euro"
      />
    </Box>
  );
}

Variants

Text-only

Used to convey a short message that requires no action, except dismiss.

import { Box, Upsell } from 'gestalt';

export default function Example() {
  return (
    <Box
      padding={8}
      height="100%"
      display="flex"
      alignItems="center"
      justifyContent="center"
    >
      <Upsell
        dismissButton={{
          accessibilityLabel: 'Dismiss this banner',
          onDismiss: () => {},
        }}
        message="Single line Upsell with no title or call to action."
      />
    </Box>
  );
}

Icon

The Icon is used to add additional meaning to the Upsell. The icon can reference a Pinterest product, feature or an action from our Icon library.

import { Box, Icon, Upsell } from 'gestalt';

export default function Example() {
  return (
    <Box
      padding={8}
      height="100%"
      display="flex"
      alignItems="center"
      justifyContent="center"
    >
      <Upsell
        dismissButton={{
          accessibilityLabel: 'Dismiss banner',
          onDismiss: () => {},
        }}
        imageData={{
          component: (
            <Icon
              icon="pinterest"
              accessibilityLabel=""
              color="default"
              size={32}
            />
          ),
        }}
        message="Earn $60 of ads credit, and give $30 of ads credit to a friend"
        primaryAction={{
          href: 'https://pinterest.com',
          label: 'Send invite',
          accessibilityLabel: 'Invite friend to use ads',
          target: 'blank',
          role: 'link',
        }}
        title="Give $30, get $60 in ads credit"
      />
    </Box>
  );
}

Image

The Image in Upsell is used to add visual interest and draw the user’s attention. Images should relate to the message of the Upsell. Upsell images should use approved photography or be illustrations using our brand colors. Images will always be 128px wide.

import { Box, Image, Upsell } from 'gestalt';

export default function Example() {
  return (
    <Box
      padding={8}
      height="100%"
      display="flex"
      alignItems="center"
      justifyContent="center"
    >
      <Upsell
        dismissButton={{
          accessibilityLabel: 'Dismiss banner',
          onDismiss: () => {},
        }}
        imageData={{
          component: (
            <Image
              alt=""
              color="rgb(231, 186, 176)"
              naturalHeight={751}
              naturalWidth={564}
              src="https://i.ibb.co/7bQQYkX/stock2.jpg"
            />
          ),
          mask: { rounding: 4 },
          width: 128,
        }}
        message="Check out our resources for adapting to these times."
        primaryAction={{
          href: 'https://pinterest.com',
          label: 'Visit',
          accessibilityLabel: 'Visit our Stay Safe resources',
          target: 'blank',
          role: 'link',
        }}
        title="Stay healthy and safe"
      />
    </Box>
  );
}

Actions

Upsells can have either one primary action, or a primary action and a secondary action. These actions can be buttons, when no href is supplied, or links, by specifying the href property.

Upsell actions with link interaction can be paired with GlobalEventsHandlerProvider. See GlobalEventsHandlerProvider to learn more about link navigation.

For example, “Learn more” may link to a separate documentation site, while “Send invite” could be a button that opens a Modal with an invite flow. Be sure to localize the labels of the actions.

If needed, actions can become disabled after clicking by setting disabled: true in the action data.

import { useState } from 'react';
import {
  Box,
  Button,
  ButtonGroup,
  Column,
  Flex,
  Label,
  Layer,
  Modal,
  Text,
  TextArea,
  TextField,
  Upsell,
} from 'gestalt';

export default function Example() {
  const [showModal, setShowModal] = useState(false);

  return (
    <Box
      padding={8}
      height="100%"
      display="flex"
      alignItems="center"
      justifyContent="center"
    >
      <Box marginStart={-1} marginEnd={-1}>
        <Upsell
          dismissButton={{
            accessibilityLabel: 'Dismiss banner',
            onDismiss: () => {},
          }}
          message="Earn $60 of ads credit, and give $30 of ads credit to a friend"
          primaryAction={{
            accessibilityLabel: 'Send ads invite',
            label: 'Send invite',
            onClick: () => {
              setShowModal(!showModal);
            },
            role: 'button',
          }}
          secondaryAction={{
            accessibilityLabel: 'Learn more: Verified Merchant Program',
            href: 'https://help.pinterest.com/en/business/article/verified-merchant-program',
            label: 'Learn more',
            target: 'blank',
            role: 'link',
          }}
          title="Give $30, get $60 in ads credit"
        />

        {showModal && (
          <Layer>
            <Modal
              accessibilityModalLabel="Invite a friend to the Verified Merchant Program"
              footer={
                <Flex flex="grow" justifyContent="end">
                  <ButtonGroup>
                    <Button
                      onClick={() => {
                        setShowModal(!showModal);
                      }}
                      size="lg"
                      text="Cancel"
                    />
                    <Button color="red" size="lg" text="Send invite" />
                  </ButtonGroup>
                </Flex>
              }
              heading="Verified Merchant Program Invitation"
              onDismiss={() => {
                setShowModal(!showModal);
              }}
              size="md"
              subHeading="When your friend spends their first $30 on ads, you’ll earn $60 of ads credit, and they’ll get $30 of ads credit, too."
            >
              <Flex direction="row">
                <Column span={12}>
                  <Box display="flex" paddingY={2} paddingX={8}>
                    <Column span={4}>
                      <Label htmlFor="name">
                        <Text align="forceLeft" weight="bold">
                          Friend&apos;s Name
                        </Text>
                      </Label>
                    </Column>

                    <Column span={8}>
                      <TextField id="name" onChange={() => undefined} />
                    </Column>
                  </Box>

                  <Box display="flex" paddingY={2} paddingX={8}>
                    <Column span={4}>
                      <Label htmlFor="email">
                        <Text align="forceLeft" weight="bold">
                          Friend&apos;s E-mail
                        </Text>
                      </Label>
                    </Column>

                    <Column span={8}>
                      <TextField id="email" onChange={() => undefined} />
                    </Column>
                  </Box>

                  <Box display="flex" paddingY={2} paddingX={8}>
                    <Column span={4}>
                      <Label htmlFor="desc">
                        <Text align="forceLeft" weight="bold">
                          Personal Message
                        </Text>
                      </Label>
                    </Column>

                    <Column span={8}>
                      <TextArea id="desc" onChange={() => undefined} />
                    </Column>
                  </Box>
                </Column>
              </Flex>
            </Modal>
          </Layer>
        )}
      </Box>
    </Box>
  );
}

Forms

Inputs can be added to Upsells to collect information from users (ex: name or email) through the use of Upsell.Form. Most Upsells should have no more than 2 inputs. If more inputs are needed, direct users to a full page using the primaryAction.

Single TextField
import { useState } from 'react';
import { Box, Icon, TextField, Upsell } from 'gestalt';

export default function FormExample() {
  const [value, setValue] = useState('');
  const handleSubmit = ({ event }) => {
    event.preventDefault();
    // your submit logic using state values
  };

  return (
    <Box
      padding={8}
      height="100%"
      display="flex"
      alignItems="center"
      justifyContent="center"
    >
      <Upsell
        dismissButton={{
          accessibilityLabel: 'Dismiss banner',
          onDismiss: () => {},
        }}
        imageData={{
          component: (
            <Icon
              icon="pinterest"
              accessibilityLabel="Pin"
              color="default"
              size={32}
            />
          ),
        }}
        message="Earn $60 of ads credit, and give $30 of ads credit to a friend"
        title="Give $30, get $60 in ads credit"
      >
        <Upsell.Form
          onSubmit={handleSubmit}
          submitButtonAccessibilityLabel="Submit name for ads credit"
          submitButtonText="Submit"
        >
          <TextField
            id="nameField"
            onChange={(e) => setValue(e.value)}
            placeholder="Name"
            value={value}
            label="Full name"
            labelDisplay="hidden"
          />
        </Upsell.Form>
      </Upsell>
    </Box>
  );
}

Multiple TextFields
import { useState } from 'react';
import { Box, Image, TextField, Upsell } from 'gestalt';

export default function Example() {
  const [nameValue, setNameValue] = useState('');
  const [emailValue, setEmailValue] = useState('');
  const handleSubmit = ({ event }) => {
    event.preventDefault();
    // your submit logic using state values
  };

  return (
    <Box
      padding={8}
      height="100%"
      display="flex"
      alignItems="center"
      justifyContent="center"
    >
      <Upsell
        dismissButton={{
          accessibilityLabel: 'Dismiss banner',
          onDismiss: () => {},
        }}
        imageData={{
          component: (
            <Image
              alt="Succulent plant against pink background"
              color="rgb(231, 186, 176)"
              naturalHeight={751}
              naturalWidth={564}
              src="https://i.ibb.co/7bQQYkX/stock2.jpg"
            />
          ),
          mask: { rounding: 4 },
          width: 128,
        }}
        message="Learn how to grow your business with a Pinterest ads expert today!"
        title="Interested in a free ads consultation?"
      >
        <Upsell.Form
          onSubmit={handleSubmit}
          submitButtonAccessibilityLabel="Submit info for contact"
          submitButtonText="Contact me"
        >
          <Box display="block" smDisplay="flex">
            <Box
              flex="grow"
              smMarginEnd={1}
              marginEnd={0}
              smMarginBottom={0}
              marginBottom={2}
            >
              <TextField
                id="name"
                onChange={({ value }) => setNameValue(value)}
                placeholder="Name"
                label="Full name"
                labelDisplay="hidden"
                value={nameValue}
              />
            </Box>

            <Box flex="grow" smMarginStart={1} marginStart={0}>
              <TextField
                id="email"
                onChange={({ value }) => setEmailValue(value)}
                placeholder="Email"
                type="email"
                label="Email address"
                labelDisplay="hidden"
                value={emailValue}
              />
            </Box>
          </Box>
        </Upsell.Form>
      </Upsell>
    </Box>
  );
}

Message

The message prop accepts either a string or Text. Use a string for simple messages without any visual style. Upsell will handle the text style and adherence to design guidelines.

If the message text requires more complex style, such as bold text or inline links, use Text to wrap your message with any additional Text or Link usages contained within. When passing in your own Text component for text, do not specify color or align Text.

import { Box, Flex, Link, Text, Upsell } from 'gestalt';

export default function Example() {
  return (
    <Box
      padding={8}
      height="100%"
      display="flex"
      alignItems="center"
      justifyContent="center"
    >
      <Flex direction="column" gap={6}>
        <Text weight="bold">Simple message string</Text>
        <Upsell
          message="Earn $60 of ads credit, and give $30 of ads credit to a friend"
          title="Give $30, get $60 in ads credit"
        />
        <Text weight="bold">Rich message with Text component</Text>
        <Upsell
          message={
            <Text inline>
              Earn $60 of ads credit, and give $30 of ads credit to a friend.{' '}
              <Link
                accessibilityLabel="Learn more about credit"
                display="inline"
                href="#Message"
              >
                Learn more
              </Link>
            </Text>
          }
          title="Give $30, get $60 in ads credit"
        />
      </Flex>
    </Box>
  );
}

Component quality checklist

Component quality checklist
Quality item
Status
Status description
Figma Library
Ready
Component is available in Figma for web and mobile web.
Responsive Web
Ready
Component responds to changing viewport sizes in web and mobile web.

Callout
Use Callout when communicating critical information, such as an error or warning. Callout can also be used to present the user with general information and further actions they can take, like the successful creation of a business account.

Toast
Toast provides feedback on a user interaction, like a confirmation that appears when a Pin has been saved. Unlike Upsell and Callout, Toasts don’t contain actions. They’re also less persistent, and disappear after a certain duration.

GlobalEventsHandlerProvider
GlobalEventsHandlerProvider allows external link navigation control across all children components with link behavior.

ActivationCard
ActivationCards are used in groups to communicate a user’s stage in a series of steps toward an overall action.