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.
vue
<template>
<Modal
sectioned
:open="active"
:primary-action="primaryAction"
:secondary-actions="secondaryActions"
@close="active = false"
>
<template #activator>
<Button
@click="active = true"
>
Open
</Button>
</template>
<template #title>
Reach more shoppers with Instagram product tags
</template>
<p>
Use Instagram posts to share your products with millions of people. Let shoppers buy from your store without leaving Instagram.
</p>
</Modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const active = ref(false);
const primaryAction = {
content: 'Add Instagram',
onAction: () => {
// Do something
},
};
const secondaryActions = [
{
content: 'Learn more',
onAction: () => {
// Do something
},
},
];
</script>
vue
<template>
<Modal
sectioned
:open="active"
:primary-action="primaryAction"
@close="active = false"
>
<template #activator>
<Button
@click="active = true"
>
Open
</Button>
</template>
<template #title>
Get a shareable link
</template>
<p>
You can share this discount link with your customers via email or social media. Your discount will be automatically applied at checkout.
</p>
</Modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const active = ref(false);
const primaryAction = {
content: 'Add Instagram',
onAction: () => {
// Do something
},
};
</script>
vue
<template>
<Modal
sectioned
:open="active"
:primary-action="primaryAction"
:secondary-actions="secondaryActions"
@close="active = false"
>
<template #activator>
<Button
@click="active = true"
>
Open
</Button>
</template>
<template #title>
Discard all unsaved changes
</template>
<p>
If you discard changes, you’ll delete any edits you made since you last saved.
</p>
</Modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const active = ref(false);
const primaryAction = {
content: 'Discard changes',
onAction: () => {
// Do something
},
destructive: true,
};
const secondaryActions = [
{
content: 'Continue editing',
onAction: () => {
// Do something
},
},
];
</script>
vue
<template>
<Modal
sectioned
:open="active"
:primary-action="primaryAction"
:secondary-actions="secondaryActions"
@close="active = false"
>
<template #activator>
<Button
@click="active = true"
>
Open
</Button>
</template>
<template #title>
Export customers
</template>
<FormLayout>
<ChoiceList
:choices="firstChoices"
v-model="firstChoice"
>
<template #title>
Export
</template>
</ChoiceList>
<ChoiceList
:choices="secondChoices"
v-model="secondChoice"
>
<template #title>
Export as
</template>
</ChoiceList>
</FormLayout>
</Modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
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 firstChoices = [
{ label: 'Current page', value: CURRENT_PAGE },
{ label: 'All customers', value: ALL_CUSTOMERS },
{ label: 'Selected customers', value: SELECTED_CUSTOMERS },
];
const secondChoices = [
{
label: 'CSV for Excel, Numbers, or other spreadsheet programs',
value: CSV_EXCEL,
},
{
label: 'Plain CSV file',
value: CSV_PLAIN,
},
];
const active = ref(false);
const firstChoice = ref(CURRENT_PAGE);
const secondChoice = ref(CSV_EXCEL);
const primaryAction = {
content: 'Export customers',
onAction: () => {
// Do something
},
};
const secondaryActions = [
{
content: 'Cancel',
onAction: () => {
// Do something
},
},
];
</script>
vue
<template>
<Modal
sectioned
size="large"
:open="active"
:primary-action="primaryAction"
:secondary-actions="secondaryActions"
@close="active = false"
>
<template #activator>
<Button
@click="active = true"
>
Open
</Button>
</template>
<template #title>
Import customers by CSV
</template>
<FormLayout>
<DropZone @drop="handleDrop">
<div v-if="files.length" :style="{padding: '0'}">
<LegacyStack vertical>
<LegacyStack
v-for="file, index in files"
:key="index"
alignment="center"
>
<Thumbnail
size="small"
:alt="file.name"
:source="validImageTypes.includes(file.type) ? getSource(file) : NoteIcon"
/>
<div>
{{ file.name }}
<Text variant="bodySm" as="p">{{ file.size }} bytes</Text>
</div>
</LegacyStack>
</LegacyStack>
</div>
<DropZoneFileUpload v-else />
</DropZone>
<Checkbox>
<template #label>
Overwrite existing customers that have the same email or phone
</template>
</Checkbox>
</FormLayout>
</Modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import NoteIcon from '@icons/NoteIcon.svg';
const validImageTypes = ['image/gif', 'image/jpeg', 'image/png'];
const active = ref(false);
const files = ref<File[]>([]);
const primaryAction = {
content: 'Import customers',
onAction: () => {
// Do something
},
};
const secondaryActions = [
{
content: 'Cancel',
onAction: () => {
// Do something
},
},
];
const handleDrop = (_dropFiles: File[], acceptedFiles: File[], _rejectedFiles: File[]) => {
files.value = [...files.value, ...acceptedFiles];
};
const getSource = (file: File) => {
return window.URL.createObjectURL(file);
}
</script>
vue
<template>
<Modal
sectioned
size="small"
:open="active"
:primary-action="primaryAction"
:secondary-actions="secondaryActions"
@close="active = false"
>
<template #activator>
<Button
@click="active = true"
>
Open
</Button>
</template>
<template #title>
Import customers by CSV
</template>
<FormLayout>
<DropZone @drop="handleDrop">
<div v-if="files.length" :style="{padding: '0'}">
<LegacyStack vertical>
<LegacyStack
v-for="file, index in files"
:key="index"
alignment="center"
>
<Thumbnail
size="small"
:alt="file.name"
:source="validImageTypes.includes(file.type) ? getSource(file) : NoteIcon"
/>
<div>
{{ file.name }}
<Text variant="bodySm" as="p">{{ file.size }} bytes</Text>
</div>
</LegacyStack>
</LegacyStack>
</div>
<DropZoneFileUpload v-else />
</DropZone>
<Checkbox>
<template #label>
Overwrite existing customers that have the same email or phone
</template>
</Checkbox>
</FormLayout>
</Modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import NoteIcon from '@icons/NoteIcon.svg';
const validImageTypes = ['image/gif', 'image/jpeg', 'image/png'];
const active = ref(false);
const files = ref<File[]>([]);
const primaryAction = {
content: 'Import customers',
onAction: () => {
// Do something
},
};
const secondaryActions = [
{
content: 'Cancel',
onAction: () => {
// Do something
},
},
];
const handleDrop = (_dropFiles: File[], acceptedFiles: File[], _rejectedFiles: File[]) => {
files.value = [...files.value, ...acceptedFiles];
};
const getSource = (file: File) => {
return window.URL.createObjectURL(file);
}
</script>
vue
<template>
<Modal
sectioned
title-hidden
:open="active"
:primary-action="primaryAction"
:secondary-actions="secondaryActions"
@close="active = false"
>
<template #activator>
<Button
@click="active = true"
>
Open
</Button>
</template>
<template #title>
Reach more shoppers with Instagram product tags
</template>
<p>
Use Instagram posts to share your products with millions of people. Let shoppers buy from your store without leaving Instagram.
</p>
</Modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const active = ref(false);
const primaryAction = {
content: 'Add Instagram',
onAction: () => {
// Do something
},
};
const secondaryActions = [
{
content: 'Learn more',
onAction: () => {
// Do something
},
},
];
</script>
vue
<template>
<Modal
sectioned
:open="active"
@close="active = false"
@scrolled-to-bottom="handleScrolledToBottom"
>
<template #activator>
<Button
@click="active = true"
>
Open
</Button>
</template>
<template #title>
Scrollable content
</template>
<TextContainer>
<p
v-for="item in 50"
:key="item"
>
Item <a href="#">#{{ item }}</a>
</p>
</TextContainer>
</Modal>
</template>
<script setup lang="ts">
import { TextContainer } from '@/components';
import { ref } from 'vue';
const active = ref(false);
const handleScrolledToBottom = () => {
alert('Scrolled to bottom!')
};
</script>
Props
No props found for this component, run `yarn gen:docs` to generate component meta first.
Slots
No slots found for this component, run `yarn gen:docs` to generate component meta first.
Events
Modal events
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, theCancel
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:
- Use a clear {verb}+{noun} question or statement
- Follow the content guidelines for headings and subheadings
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".
- 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.
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.
- 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.
- Scannable: avoid unnecessary words and articles such as the, an, or a.
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
Footer
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".
- 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.
Related components
- 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 thetitle
is used to label the dialog element witharia-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, theCancel
button if one is provided, or by pressing the Esc key - After a modal is closed, focus returns to the button that launched it