Sheet
The Sheet component displays essential information for customers at the bottom of the screen, appearing above other elements. Use it sparingly to avoid distracting customers during checkout. This component requires access to Customer Privacy API to be rendered.
The library automatically applies the WAI-ARIA Dialog pattern to both the activator and the sheet content.
Anchor to sheetpropsSheetProps
- Anchor to accessibilityLabelaccessibilityLabelstring
A label to describe the purpose of the sheet that is announced by screen readers. If not set, it will use the value of
heading
.- Anchor to defaultOpendefaultOpenboolean
Indicates whether the sheet should be open by default.
This property is necessary in some cases, but its usage is generally discouraged due to potential negative impacts on user experience.
Developers should:
- Only set this property to true when there are vitally important behaviors of the application that depend on the user interacting with the sheet.
- Make every effort to conditionally hide the sheet based on the state of checkout. An explicit example is custom privacy consent, where the sheet should only be displayed when consent is necessary and has not yet been explicitly given by the user.
This property is useful for when the Sheet needs to be rendered on the page load and not triggered by a user action. The property should only take effect when the
Sheet
is rendered for the first time.- Anchor to headingheadingstring
A heading rendered at the top of the sheet.
- string
A unique identifier for the component.
- Anchor to onHideonHide() => void
Callback fired when the sheet is closed.
- Anchor to onShowonShow() => void
Callback fired when the sheet is opened.
- Anchor to primaryActionprimaryActionRemoteFragment
The primary action to perform, provided as a
Button
component. The property allows up to two buttons to be rendered.- Anchor to secondaryActionsecondaryActionRemoteFragment
The secondary action to perform, provided as a
Button
component. The property allows only one button to be rendered.
SheetProps
- accessibilityLabel
A label to describe the purpose of the sheet that is announced by screen readers. If not set, it will use the value of `heading`.
string
- defaultOpen
Indicates whether the sheet should be open by default. This property is necessary in some cases, but its usage is generally discouraged due to potential negative impacts on user experience. Developers should: - Only set this property to true when there are vitally important behaviors of the application that depend on the user interacting with the sheet. - Make every effort to conditionally hide the sheet based on the state of checkout. An explicit example is custom privacy consent, where the sheet should only be displayed when consent is necessary and has not yet been explicitly given by the user. This property is useful for when the Sheet needs to be rendered on the page load and not triggered by a user action. The property should only take effect when the `Sheet` is rendered for the first time.
boolean
- heading
A heading rendered at the top of the sheet.
string
- id
A unique identifier for the component.
string
- onHide
Callback fired when the sheet is closed.
() => void
- onShow
Callback fired when the sheet is opened.
() => void
- primaryAction
The primary action to perform, provided as a `Button` component. The property allows up to two buttons to be rendered.
RemoteFragment
- secondaryAction
The secondary action to perform, provided as a `Button` component. The property allows only one button to be rendered.
RemoteFragment
export interface SheetProps extends IdProps {
/**
* A label to describe the purpose of the sheet that is announced by screen readers.
* If not set, it will use the value of `heading`.
*/
accessibilityLabel?: string;
/**
* Indicates whether the sheet should be open by default.
*
* This property is necessary in some cases, but its usage is generally discouraged due to potential negative impacts on user experience.
*
* Developers should:
* - Only set this property to true when there are vitally important behaviors of the application that depend on the user interacting with the sheet.
* - Make every effort to conditionally hide the sheet based on the state of checkout. An explicit example is custom privacy consent, where the sheet should only be displayed when consent is necessary and has not yet been explicitly given by the user.
*
* This property is useful for when the Sheet needs to be rendered on the page load and not triggered by a user action.
* The property should only take effect when the `Sheet` is rendered for the first time.
*/
defaultOpen?: boolean;
/** A heading rendered at the top of the sheet. */
heading?: string;
/** Callback fired when the sheet is opened. */
onShow?(): void;
/** Callback fired when the sheet is closed. */
onHide?(): void;
/**
* The primary action to perform, provided as a `Button` component.
* The property allows up to two buttons to be rendered.
*/
primaryAction?: RemoteFragment;
/**
* The secondary action to perform, provided as a `Button` component.
* The property allows only one button to be rendered.
*/
secondaryAction?: RemoteFragment;
}
Basic Sheet
examples
Basic Sheet
React
import { reactExtension, Link, Sheet, TextBlock, } from '@shopify/ui-extensions-react/checkout'; // This component requires access to Customer Privacy API to be rendered. export default reactExtension( 'purchase.checkout.block.render', () => <Extension />, ); function Extension() { return ( <Link overlay={ <Sheet id="basic-sheet" heading="Basic Sheet" accessibilityLabel="A sheet with text content" > <TextBlock> Basic Sheet Content </TextBlock> </Sheet> } > Open sheet </Link> ); }
JS
import { extension, Link, Sheet, TextBlock, } from '@shopify/ui-extensions/checkout'; // This component requires access to Customer Privacy API to be rendered. export default extension('purchase.checkout.block.render', (root) => { const sheetFragment = root.createFragment(); const sheet = root.createComponent( Sheet, { id: 'basic-sheet', heading: 'Basic Sheet', accessibilityLabel: 'A sheet with text content', }, [root.createComponent(TextBlock, undefined, 'Basic Sheet Content')], ); sheetFragment.appendChild(sheet); const link = root.createComponent( Link, {overlay: sheetFragment}, 'Open sheet', ); root.appendChild(link); });
Preview

Anchor to shopify-controlled-surfacesShopify-controlled surfaces
To prevent disruptions during checkout, we maintain strict design control over key areas of the Sheet component. These Shopify-controlled elements include:
Locations of elements
The Sheet elements (header, content, action buttons, and dismiss button) are strategically positioned and sized to present vital information upfront.


Padding and spacing

Maximum height
To balance customer attention and task completion, a maximum height is set for the Sheet component.


When content pushes the sheet to exceed this limit, the following UI behaviors are triggered:
Heading and content are scrollable

Expand pill appears to allow customers to view the entire content

Actions slot and dismiss button remain fixed

Anchor to privacy-consent-requirementsPrivacy consent requirements
Content
For the best customer experience, ensure content is brief and to the point.

Various strategies can be employed to avoid content scrolling.
Use short content

Use small text size

Remove the header

Actions slot
The actions slots allows customers to make decisions and is split into primary and secondary sections.

Primary section
Contains primary actions for customer decisions on the sheet’s prompt. Up to two buttons are allowed. Keep the button’s content brief so that it doesn’t wrap to more than one line.

Secondary section
Contains action that is unrelated to the sheet’s prompt. Only one button is allowed. A modal can be activated when engaging with the secondary action. Keep the button’s content brief so that it doesn’t wrap to more than one line.

Consent, denial of consent, and sheet dismissal
Consent
When a customer expresses consent by pressing the acceptance button, cookies will load and the sheet should not re-appear on refresh.
Denial of consent
When a customer expresses denial of consent by pressing the rejection button, cookies will not load and the sheet will not re-appear on refresh.
Sheet dismissal
When a customer neither grants nor denies consent by pressing the dismiss button, cookies will not load and the sheet will re-appear on refresh.

Anchor to examplesExamples
Anchor to example-using-sheet-to-display-consent-preferencesUsing Sheet to display consent preferences
The Sheet component can be used to display privacy consent preferences in the Checkout interface. Sheet can be defaulted to open for this use case.
This component requires access to Customer Privacy API to be rendered.
Anchor to example-using-layout-component-in-the-description-Using layout component in the description
The description can take in layout components to allow for different types of content to be structured in specific ways.
Using Sheet to display consent preferences
examples
Using Sheet to display consent preferences
description
The Sheet component can be used to display privacy consent preferences in the Checkout interface. Sheet can be defaulted to open for this use case. This component requires access to [Customer Privacy API](/docs/api/checkout-ui-extensions/2025-07/apis/customer-privacy) to be rendered.
React
import { reactExtension, Button, Link, Sheet, TextBlock, useApi, useCustomerPrivacy, } from '@shopify/ui-extensions-react/checkout'; export default reactExtension( 'purchase.checkout.footer.render-after', () => <Extension />, ); function Extension() { const {applyTrackingConsentChange, ui} = useApi(); const {shouldShowBanner} = useCustomerPrivacy(); const sheetId = 'sheet-consent'; const handleConsentChange = async ({ analytics, marketing, preferences, saleOfData, }) => { try { const result = await applyTrackingConsentChange({ type: 'changeVisitorConsent', analytics, marketing, preferences, saleOfData, }); // Check if operation was successful if (result.type === 'success') { ui.overlay.close(sheetId); } else { // Handle failure case here } } catch (error) { // Handle error case here } }; return ( <Sheet id={sheetId} heading="We value your privacy" accessibilityLabel="A sheet that collects privacy consent preferences" defaultOpen={shouldShowBanner} primaryAction={ <> <Button kind="secondary" onPress={() => handleConsentChange({ // values derived from local form state analytics: false, marketing: false, preferences: false, saleOfData: false, }) } > I decline </Button> <Button kind="secondary" onPress={() => handleConsentChange({ analytics: true, marketing: true, preferences: true, saleOfData: true, }) } > I agree </Button> </> } secondaryAction={ <Button kind="plain" overlay={ // Open a settings modal } > Settings </Button> } > <TextBlock> This website uses cookies to ensure you get the best experience on our website. <Link>Privacy Policy</Link> </TextBlock> </Sheet> ); }
JavaScript
import { extension, Sheet, Button, Link, TextBlock, } from '@shopify/ui-extensions/checkout'; export default extension( 'purchase.checkout.footer.render-after', ( root, { applyTrackingConsentChange, customerPrivacy, ui, }, ) => { customerPrivacy.subscribe( ({shouldShowBanner}) => { const primaryFragment = root.createFragment(); const secondaryFragment = root.createFragment(); const handleConsentChange = async ({ analytics, marketing, preferences, saleOfData, }) => { try { const result = await applyTrackingConsentChange({ type: 'changeVisitorConsent', analytics, marketing, preferences, saleOfData, }); // Check if operation was successful if (result) { ui.overlay.close(sheetId); } else { // Handle failure case here } } catch (error) { // Handle error case here } }; const declineButton = root.createComponent( Button, { kind: 'secondary', onPress: () => handleConsentChange({ analytics: false, marketing: false, preferences: false, saleOfData: false, }), }, 'I decline', ); const agreeButton = root.createComponent( Button, { kind: 'secondary', onPress: () => handleConsentChange({ analytics: true, marketing: true, preferences: true, saleOfData: true, }), }, 'I agree', ); const settingsButton = root.createComponent( Button, { kind: 'secondary', }, 'Settings', ); primaryFragment.appendChild( declineButton, ); primaryFragment.appendChild(agreeButton); secondaryFragment.appendChild( settingsButton, ); const sheetId = 'sheet-consent'; const sheet = root.createComponent( Sheet, { id: sheetId, heading: 'We value your privacy', accessibilityLabel: 'A sheet that collects privacy consent preferences', defaultOpen: shouldShowBanner, primaryAction: primaryFragment, secondaryAction: secondaryFragment, }, ); const textBlock = root.createComponent( TextBlock, null, [ 'We and our partners use cookies and other technologies to improve your experience, measure performance, and tailor marketing. Details in our ', root.createComponent( Link, null, 'Privacy Policy', ), ], ); sheet.appendChild(textBlock); root.appendChild(sheet); }, ); }, );
Preferences button is in the description as a link
description
In order to save space in the action slot, secondary actions can be placed in the content area.
React
import { reactExtension, Button, BlockStack, Link, Sheet, TextBlock, useCustomerPrivacy, } from '@shopify/ui-extensions-react/checkout'; export default reactExtension( 'purchase.checkout.footer.render-after', () => <Extension />, ); function Extension() { const {shouldShowBanner} = useCustomerPrivacy(); const sheetId = 'sheet-consent'; return ( <Sheet id={sheetId} accessibilityLabel="A sheet that collects privacy consent preferences" defaultOpen={shouldShowBanner} primaryAction={ <> <Button kind="secondary" onPress={() => {}} > I decline </Button> <Button kind="secondary" onPress={() => {}} > I agree </Button> </> } > <BlockStack spacing="none"> <TextBlock> This website uses cookies to ensure you get the best experience on our website. </TextBlock> <TextBlock> <Link>Privacy Policy</Link> ‧{' '} <Link>Cookie Policy</Link> ‧{' '} <Link // overlay: <Modal>Preferences modal...</Modal>, > Preferences </Link> </TextBlock> </BlockStack> </Sheet> ); }
JavaScript
import { extension, Sheet, Button, Link, TextBlock, BlockStack, } from '@shopify/ui-extensions/checkout'; export default extension( 'purchase.checkout.footer.render-after', (root, {customerPrivacy}) => { const primaryFragment = root.createFragment(); const declineButton = root.createComponent( Button, { kind: 'secondary', onPress: () => {}, }, 'I decline', ); const agreeButton = root.createComponent( Button, { kind: 'secondary', onPress: () => {}, }, 'I agree', ); primaryFragment.appendChild(declineButton); primaryFragment.appendChild(agreeButton); const sheetId = 'sheet-consent'; const sheet = root.createComponent(Sheet, { id: sheetId, accessibilityLabel: 'A sheet that collects privacy consent preferences', defaultOpen: customerPrivacy.current.shouldShowBanner, primaryAction: primaryFragment, }); const textBlock = root.createComponent( TextBlock, null, 'This website uses cookies to ensure you get the best experience on our website.', ); const linkBlock = root.createComponent( TextBlock, null, [ root.createComponent( Link, null, 'Privacy Policy', ), ' ‧ ', root.createComponent( Link, null, 'Cookie Policy', ), ' ‧ ', root.createComponent( Link, { // overlay: <Modal>Preferences modal...</Modal>,, }, 'Preferences', ), ], ); const blockStack = root.createComponent( BlockStack, {spacing: 'none'}, [textBlock, linkBlock], ); sheet.appendChild(blockStack); root.appendChild(sheet); }, );
Icon button used for preferences
description
An icon button can be used in the secondary actions area to allow for more space for the primary actions.
React
import { reactExtension, Button, Link, Icon, Sheet, TextBlock, useCustomerPrivacy, } from '@shopify/ui-extensions-react/checkout'; export default reactExtension( 'purchase.checkout.footer.render-after', () => <Extension />, ); function Extension() { const {shouldShowBanner} = useCustomerPrivacy(); return ( <Sheet accessibilityLabel="A sheet that collects privacy consent preferences" heading="We value your privacy" defaultOpen={shouldShowBanner} primaryAction={ <> <Button kind="secondary" onPress={() => {}} > I decline </Button> <Button kind="secondary" onPress={() => {}} > I agree </Button> </> } secondaryAction={ <Button kind="secondary" onPress={() => {}} > <Icon source="settings" /> </Button> } > <TextBlock> This website uses cookies to ensure you get the best experience on our website.{' '} <Link>Privacy Policy</Link>. </TextBlock> </Sheet> ); }
JavaScript
import { extension, Sheet, Button, Link, TextBlock, Icon, } from '@shopify/ui-extensions/checkout'; export default extension( 'purchase.checkout.footer.render-after', (root, {customerPrivacy}) => { const primaryFragment = root.createFragment(); const secondaryFragment = root.createFragment(); const declineButton = root.createComponent( Button, { kind: 'secondary', onPress: () => {}, }, 'I decline', ); const agreeButton = root.createComponent( Button, { kind: 'secondary', onPress: () => {}, }, 'I agree', ); const preferencesButton = root.createComponent( Button, { kind: 'secondary', }, root.createComponent(Icon, { source: 'settings', }), ); primaryFragment.appendChild(declineButton); primaryFragment.appendChild(agreeButton); secondaryFragment.appendChild( preferencesButton, ); const sheet = root.createComponent(Sheet, { accessibilityLabel: 'A sheet that collects privacy consent preferences', defaultOpen: customerPrivacy.current.shouldShowBanner, primaryAction: primaryFragment, secondaryAction: secondaryFragment, }); const textBlock = root.createComponent( TextBlock, null, [ 'This website uses cookies to ensure you get the best experience on our website. ', root.createComponent( Link, null, 'Privacy Policy', ), ], ); sheet.appendChild(textBlock); root.appendChild(sheet); }, );
Using layout component in the description
description
The description can take in layout components to allow for different types of content to be structured in specific ways.
React
import { reactExtension, Button, Link, Image, InlineLayout, Sheet, TextBlock, useCustomerPrivacy, } from '@shopify/ui-extensions-react/checkout'; export default reactExtension( 'purchase.checkout.footer.render-after', () => <Extension />, ); function Extension() { const {shouldShowBanner} = useCustomerPrivacy(); return ( <Sheet accessibilityLabel="A sheet that collects privacy consent preferences" defaultOpen={shouldShowBanner} primaryAction={ <> <Button kind="secondary" onPress={() => {}} > I decline </Button> <Button kind="secondary" onPress={() => {}} > I agree </Button> </> } secondaryAction={ <Button kind="plain" onPress={() => {}}> Preferences </Button> } > <InlineLayout padding="none" spacing="small100" columns={[38, 'fill']} > <Image source="https://yourawesomeimage.com" /> <TextBlock> This website uses cookies to ensure you get the best experience on our website.{' '} <Link>Learn more</Link>. </TextBlock> </InlineLayout> </Sheet> ); }
JavaScript
import { extension, Sheet, Button, Link, TextBlock, Image, InlineLayout, } from '@shopify/ui-extensions/checkout'; export default extension( 'purchase.checkout.footer.render-after', (root, {customerPrivacy}) => { const primaryFragment = root.createFragment(); const secondaryFragment = root.createFragment(); const declineButton = root.createComponent( Button, { kind: 'secondary', onPress: () => {}, }, 'I decline', ); const agreeButton = root.createComponent( Button, { kind: 'secondary', onPress: () => {}, }, 'I agree', ); const preferencesButton = root.createComponent( Button, { kind: 'plain', }, 'Preferences', ); primaryFragment.appendChild(declineButton); primaryFragment.appendChild(agreeButton); secondaryFragment.appendChild( preferencesButton, ); const sheet = root.createComponent(Sheet, { accessibilityLabel: 'A sheet that collects privacy consent preferences', defaultOpen: customerPrivacy.current.shouldShowBanner, primaryAction: primaryFragment, secondaryAction: secondaryFragment, }); const textBlock = root.createComponent( TextBlock, null, [ 'This website uses cookies to ensure you get the best experience on our website.', root.createComponent( Link, null, 'Learn more', ), ], ); const inlineLayout = root.createComponent( InlineLayout, { padding: 'none', spacing: 'small100', columns: [38, 'fill'], }, [ root.createComponent(Image, { source: 'https://yourawesomeimage.com', }), textBlock, ], ); sheet.appendChild(inlineLayout); root.appendChild(sheet); }, );