Skip to contentShopify logoPolaris

Modal

Modals are overlays that require merchants to take an action before they can continue interacting with the rest of Shopify. They can be disruptive and should be used thoughtfully and sparingly.

Use as the default option for a modal.

import {Button, Modal, TextContainer} from '@shopify/polaris';
import {useState, useCallback} from 'react';

function ModalExample() {
  const [active, setActive] = useState(true);

  const handleChange = useCallback(() => setActive(!active), [active]);

  const activator = <Button onClick={handleChange}>Open</Button>;

  return (
    <div style={{height: '500px'}}>
      <Modal
        activator={activator}
        open={active}
        onClose={handleChange}
        title="Reach more shoppers with Instagram product tags"
        primaryAction={{
          content: 'Add Instagram',
          onAction: handleChange,
        }}
        secondaryActions={[
          {
            content: 'Learn more',
            onAction: handleChange,
          },
        ]}
      >
        <Modal.Section>
          <TextContainer>
            <p>
              Use Instagram posts to share your products with millions of
              people. Let shoppers buy from your store without leaving
              Instagram.
            </p>
          </TextContainer>
        </Modal.Section>
      </Modal>
    </div>
  );
}

Use to let merchants take a key action.

import {Button, Modal, Stack, TextContainer, TextField} from '@shopify/polaris';
import {useState, useCallback, useRef} from 'react';

function ModalWithPrimaryActionExample() {
  const DISCOUNT_LINK = 'https://polaris.shopify.com/';

  const [active, setActive] = useState(true);
  const node = useRef(null);

  const handleClick = useCallback(() => {
    node.current && node.current.input.focus();
  }, []);

  const handleFocus = useCallback(() => {
    if (node.current == null) {
      return;
    }
    node.current.input.select();
    document.execCommand('copy');
  }, []);

  const toggleModal = useCallback(() => setActive((active) => !active), []);

  const activator = <Button onClick={toggleModal}>Open</Button>;

  return (
    <div style={{height: '500px'}}>
      <Modal
        activator={activator}
        open={active}
        onClose={toggleModal}
        title="Get a shareable link"
        primaryAction={{
          content: 'Close',
          onAction: toggleModal,
        }}
      >
        <Modal.Section>
          <Stack vertical>
            <Stack.Item>
              <TextContainer>
                <p>
                  You can share this discount link with your customers via email
                  or social media. Your discount will be automatically applied
                  at checkout.
                </p>
              </TextContainer>
            </Stack.Item>
            <Stack.Item fill>
              <TextField
                ref={node}
                label="Discount link"
                onFocus={handleFocus}
                value={DISCOUNT_LINK}
                onChange={() => {}}
                autoComplete="off"
                connectedRight={
                  <Button primary onClick={handleClick}>
                    Copy link
                  </Button>
                }
              />
            </Stack.Item>
          </Stack>
        </Modal.Section>
      </Modal>
    </div>
  );
}

Use to let merchants take key actions at the bottom of the modal.

import {Button, Modal, Stack, ChoiceList} from '@shopify/polaris';
import {useState, useCallback} from 'react';

function ModalWithPrimaryAndSecondaryActionsExample() {
  const CURRENT_PAGE = 'current_page';
  const ALL_CUSTOMERS = 'all_customers';
  const SELECTED_CUSTOMERS = 'selected_customers';
  const CSV_EXCEL = 'csv_excel';
  const CSV_PLAIN = 'csv_plain';

  const [active, setActive] = useState(true);
  const [selectedExport, setSelectedExport] = useState([]);
  const [selectedExportAs, setSelectedExportAs] = useState([]);

  const handleModalChange = useCallback(() => setActive(!active), [active]);

  const handleClose = () => {
    handleModalChange();
    handleSelectedExport([]);
    handleSelectedExportAs([]);
  };

  const handleSelectedExport = useCallback(
    (value) => setSelectedExport(value),
    [],
  );

  const handleSelectedExportAs = useCallback(
    (value) => setSelectedExportAs(value),
    [],
  );

  const activator = <Button onClick={handleModalChange}>Open</Button>;

  return (
    <div style={{height: '500px'}}>
      <Modal
        activator={activator}
        open={active}
        onClose={handleClose}
        title="Export customers"
        primaryAction={{
          content: 'Export customers',
          onAction: handleClose,
        }}
        secondaryActions={[
          {
            content: 'Cancel',
            onAction: handleClose,
          },
        ]}
      >
        <Modal.Section>
          <Stack vertical>
            <Stack.Item>
              <ChoiceList
                title="Export"
                choices={[
                  {label: 'Current page', value: CURRENT_PAGE},
                  {label: 'All customers', value: ALL_CUSTOMERS},
                  {label: 'Selected customers', value: SELECTED_CUSTOMERS},
                ]}
                selected={selectedExport}
                onChange={handleSelectedExport}
              />
            </Stack.Item>
            <Stack.Item>
              <ChoiceList
                title="Export as"
                choices={[
                  {
                    label:
                      'CSV for Excel, Numbers, or other spreadsheet programs',
                    value: CSV_EXCEL,
                  },
                  {label: 'Plain CSV file', value: CSV_PLAIN},
                ]}
                selected={selectedExportAs}
                onChange={handleSelectedExportAs}
              />
            </Stack.Item>
          </Stack>
        </Modal.Section>
      </Modal>
    </div>
  );
}

Use when you need to increase the width of your modal.

import {Button, Modal, Stack, DropZone, Checkbox} from '@shopify/polaris';
import {useState, useCallback} from 'react';

function LargeModalExample() {
  const [active, setActive] = useState(true);
  const [checked, setChecked] = useState(false);

  const toggleActive = useCallback(() => setActive((active) => !active), []);

  const handleCheckbox = useCallback((value) => setChecked(value), []);

  const activator = <Button onClick={toggleActive}>Open</Button>;

  return (
    <div style={{height: '500px'}}>
      <Modal
        large
        activator={activator}
        open={active}
        onClose={toggleActive}
        title="Import customers by CSV"
        primaryAction={{
          content: 'Import customers',
          onAction: toggleActive,
        }}
        secondaryActions={[
          {
            content: 'Cancel',
            onAction: toggleActive,
          },
        ]}
      >
        <Modal.Section>
          <Stack vertical>
            <DropZone
              accept=".csv"
              errorOverlayText="File type must be .csv"
              type="file"
              onDrop={() => {}}
            >
              <DropZone.FileUpload />
            </DropZone>
            <Checkbox
              checked={checked}
              label="Overwrite existing customers that have the same email or phone"
              onChange={handleCheckbox}
            />
          </Stack>
        </Modal.Section>
      </Modal>
    </div>
  );
}

Use when you need to decrease the width of your modal.

import {Button, Modal, Stack, DropZone, Checkbox} from '@shopify/polaris';
import {useState, useCallback} from 'react';

function SmallModalExample() {
  const [active, setActive] = useState(true);
  const [checked, setChecked] = useState(false);

  const toggleActive = useCallback(() => setActive((active) => !active), []);

  const handleCheckbox = useCallback((value) => setChecked(value), []);

  const activator = <Button onClick={toggleActive}>Open</Button>;

  return (
    <div style={{height: '500px'}}>
      <Modal
        small
        activator={activator}
        open={active}
        onClose={toggleActive}
        title="Import customers by CSV"
        primaryAction={{
          content: 'Import customers',
          onAction: toggleActive,
        }}
        secondaryActions={[
          {
            content: 'Cancel',
            onAction: toggleActive,
          },
        ]}
      >
        <Modal.Section>
          <Stack vertical>
            <DropZone
              accept=".csv"
              errorOverlayText="File type must be .csv"
              type="file"
              onDrop={() => {}}
            >
              <DropZone.FileUpload />
            </DropZone>
            <Checkbox
              checked={checked}
              label="Overwrite existing customers that have the same email or phone"
              onChange={handleCheckbox}
            />
          </Stack>
        </Modal.Section>
      </Modal>
    </div>
  );
}

A title is required for accessibility, but you may hide it.

import {Button, Modal, TextContainer} from '@shopify/polaris';
import {useState, useCallback} from 'react';

function ModalWithoutTitleExample() {
  const [active, setActive] = useState(true);

  const handleChange = useCallback(() => setActive(!active), [active]);

  const activator = <Button onClick={handleChange}>Open</Button>;

  return (
    <div style={{height: '500px'}}>
      <Modal
        title="Reach more shoppers with Instagram product tags"
        titleHidden
        activator={activator}
        open={active}
        onClose={handleChange}
        primaryAction={{
          content: 'Add Instagram',
          onAction: handleChange,
        }}
        secondaryActions={[
          {
            content: 'Learn more',
            onAction: handleChange,
          },
        ]}
      >
        <Modal.Section>
          <TextContainer>
            <p>
              Use Instagram posts to share your products with millions of
              people. Let shoppers buy from your store without leaving
              Instagram.
            </p>
          </TextContainer>
        </Modal.Section>
      </Modal>
    </div>
  );
}

Use to implement infinite scroll of modal content.

import {Button, Modal, TextContainer} from '@shopify/polaris';
import {useState, useCallback} from 'react';

function ModalWithScrollListenerExample() {
  const [active, setActive] = useState(true);

  const handleChange = useCallback(() => setActive(!active), [active]);

  const handleScrollBottom = useCallback(() => alert('Scrolled to bottom'), []);

  const activator = <Button onClick={handleChange}>Open</Button>;

  return (
    <div style={{height: '500px'}}>
      <Modal
        activator={activator}
        open={active}
        title="Scrollable content"
        onClose={handleChange}
        onScrolledToBottom={handleScrollBottom}
      >
        {Array.from({length: 50}, (_, index) => (
          <Modal.Section key={index}>
            <TextContainer>
              <p>
                Item <a href="#">#{index}</a>
              </p>
            </TextContainer>
          </Modal.Section>
        ))}
      </Modal>
    </div>
  );
}

Provide an activator ref when it’s more convenient than providing an element. This ensures proper focus management when closing the modal. See the accessibility features of a modal for more information regarding focus.

import {Button, Modal, TextContainer} from '@shopify/polaris';
import {useState, useCallback, useRef} from 'react';

function ModalExample() {
  const [active, setActive] = useState(true);

  const buttonRef = useRef(null);

  const handleOpen = useCallback(() => setActive(true), []);

  const handleClose = useCallback(() => {
    setActive(false);
  }, []);

  const activator = (
    <div ref={buttonRef}>
      <Button onClick={handleOpen}>Open</Button>
    </div>
  );

  return (
    <div style={{height: '500px'}}>
      {activator}
      <Modal
        activator={buttonRef}
        open={active}
        onClose={handleClose}
        title="Reach more shoppers with Instagram product tags"
        primaryAction={{
          content: 'Add Instagram',
          onAction: handleClose,
        }}
        secondaryActions={[
          {
            content: 'Learn more',
            onAction: handleClose,
          },
        ]}
      >
        <Modal.Section>
          <TextContainer>
            <p>
              Use Instagram posts to share your products with millions of
              people. Let shoppers buy from your store without leaving
              Instagram.
            </p>
          </TextContainer>
        </Modal.Section>
      </Modal>
    </div>
  );
}

Use an external activator when technical limitations prevent you from passing the activator as an element or a ref. Make sure to focus the activator on close when choosing this approach. See the accessibility features of a modal for more information regarding focus.

import {Button, Modal, TextContainer} from '@shopify/polaris';
import {useState, useCallback, useRef} from 'react';

function ModalExample() {
  const [active, setActive] = useState(true);

  const button = useRef();

  const handleOpen = useCallback(() => setActive(true), []);

  const handleClose = useCallback(() => {
    setActive(false);
    requestAnimationFrame(() => button.current.querySelector('button').focus());
  }, []);

  return (
    <div style={{height: '500px'}}>
      <div ref={button}>
        <Button onClick={handleOpen}>Open</Button>
      </div>
      <Modal
        instant
        open={active}
        onClose={handleClose}
        title="Reach more shoppers with Instagram product tags"
        primaryAction={{
          content: 'Add Instagram',
          onAction: handleClose,
        }}
        secondaryActions={[
          {
            content: 'Learn more',
            onAction: handleClose,
          },
        ]}
      >
        <Modal.Section>
          <TextContainer>
            <p>
              Use Instagram posts to share your products with millions of
              people. Let shoppers buy from your store without leaving
              Instagram.
            </p>
          </TextContainer>
        </Modal.Section>
      </Modal>
    </div>
  );
}

Props

Want to help make this feature better? Please share your feedback.

interface ModalProps
openboolean

Whether the modal is open or not.

src?string

The url that will be loaded as the content of the modal.

iFrameName?string

The name of the modal content iframe.

titleany

The content for the title of the modal.

titleHidden?boolean

Hide the title in the modal.

Defaults to false.

children?React.ReactNode

The content to display inside modal.

footer?React.ReactNode

Inner content of the footer.

instant?boolean

Disable animations and open modal instantly.

sectioned?boolean

Automatically adds sections to modal.

large?boolean

Increases the modal width.

small?boolean

Decreases the modal width.

limitHeight?boolean

Limits modal height on large sceens with scrolling.

loading?boolean

Replaces modal content with a spinner while a background action is being performed.

onClose() => void

Callback when the modal is closed.

onIFrameLoad?(evt: React.SyntheticEvent<HTMLIFrameElement>) => void

Callback when iframe has loaded.

onTransitionEnd?() => void

Callback when modal transition animation has ended.

onScrolledToBottom?() => void

Callback when the bottom of the modal content is reached.

activator?any

The element or the RefObject that activates the Modal.

noScroll?boolean

Removes Scrollable container from the modal content.

fullScreen?boolean

Sets modal to the height of the viewport on small screens.

primaryAction?

Primary action.

secondaryActions?[]

Collection of secondary actions.

Best practices

Use modals for confirmations and conditional changes. They should be thought of as temporary and not be used for information or actions that need to live on in the UI in a persistent way. Don’t use modals to display complex forms or large amounts of information.

Modals should:

  • Require that merchants take an action.
  • Close when merchants press the X button, the Cancel button, the Esc key, or when merchants click or tap the area outside the modal.
  • Not have more than two buttons (primary and secondary) at the bottom. This prevents unclear action hierarchy and crowding on mobile screens. Since modals are for focused tasks, they should have focused actions. In some cases however, a tertiary action may be appropriate.

Content guidelines

Title

Modal titles should:

Do

  • Edit email address
  • Delete customer?
  • Discard unsaved changes?

Don’t

  • Edit the email address for this order
  • Are you sure you want to delete customer?
  • Discard?

Body content

Body content should be:

  • Actionable: start sentences with imperative verbs when telling a merchant what actions are available to them (especially something new). Don’t use permissive language like "you can".

Do

  • Notification emails will be sent to this address.
  • This can’t be undone.

Don’t

  • You can edit the email address where emails will be sent.
  • Are you sure you want to delete the variant Dark Blue Tee/Small/Silk? You cannot reverse this.
  • Structured for merchant success: always put the most critical information first.
  • Clear: use the verb “need” to help merchants understand when they’re required to do something.

Do

  • To buy a shipping label, you need to enter the total weight of your shipment, including packaging.

Don’t

  • To buy a shipping label, you must enter the total weight of your shipment, including packaging.

Primary and secondary actions

Actions should be:

  • Clear and predictable: merchants should be able to anticipate what will happen when they click a button. Never deceive a merchant by mislabeling an action.

Do

  • Create order
  • Buy shipping label

Don’t

  • New order
  • Buy
  • Action-led: actions should always lead with a strong verb that encourages action. To provide enough context to merchants use the {verb}+{noun} format on actions except in the case of common actions like Save, Close, Cancel, or OK.

Do

  • Activate Apple Pay
  • View shipping settings

Don’t

  • Try Apple Pay
  • View your settings
  • Scannable: avoid unnecessary words and articles such as the, an, or a.

Do

  • Add menu item

Don’t

  • Add a menu item

Tertiary actions

Tertiary actions should:

  • Only be used when the action requires the context of the content in the modal
  • Never be used to dismiss the modal

Do

  • Use a plain button for a tertiary action if needed Screenshot of modal with a plain button as a tertiary action

Don’t

  • Use a tertiary action for a destructive action Screenshot of modal with a destructive button as a tertiary action

Body content should be:

  • Actionable: start sentences with imperative verbs when telling a merchant what actions are available to them (especially something new). Don’t use permissive language like "you can".

Do

  • Notification emails will be sent to this address.

Don’t

  • You can edit the email address where emails will be sent.
  • Structured for merchant success: always put the most critical information first.
  • Clear: use the verb “need” to help merchants understand when they’re required to do something.

Do

  • To buy a shipping label, you need to enter the total weight of your shipment, including packaging.

Don’t

  • To buy a shipping label, you must enter the total weight of your shipment, including packaging.

  • To present large amounts of additional information or actions that don’t require confirmation, use the collapsible component to expand content in place within the page
  • To present a small amount of content or a menu of actions in a non-blocking overlay, use the popover component
  • To communicate a change or condition that needs the merchant’s attention within the context of a page, use the banner component

Accessibility

  • Modals use ARIA role=”dialog” to convey to screen reader users that they work like native dialog windows.
  • If you set the title prop to give the modal component a heading, then the title is used to label the dialog element with aria-labelledby. This helps to convey the purpose of the modal to screen reader users when it displays.
  • After a modal is closed, in order to return focus to the button that launched it, pass the button to the modal as an activator.

Keyboard support

  • When a modal opens, focus moves automatically to the modal container so it can be accessed by keyboard users
  • While the modal is open, keyboard focus shouldn’t leave the modal
  • Merchants can dismiss the modal with the keyboard by activating the X button, the Cancel button if one is provided, or by pressing the Esc key
  • After a modal is closed, focus returns to the button that launched it