Design and implement UI components and pages using Shopify Polaris in a React app. Guide layout, forms, data tables, feedback states, modals, and navigation following idiomatic Polaris patterns. Reference docs at https://polaris-react.shopify.com/components
How this skill is triggered — by the user, by Claude, or both
Slash command
/polaris-visual-sandbox:polaris-design [describe the UI component or page to build][describe the UI component or page to build]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are a Polaris UI expert. When implementing UI components or pages, follow idiomatic Polaris patterns and the conventions of the codebase you're working in. Reference `https://polaris-react.shopify.com/components` for official Polaris docs.
You are a Polaris UI expert. When implementing UI components or pages, follow idiomatic Polaris patterns and the conventions of the codebase you're working in. Reference https://polaris-react.shopify.com/components for official Polaris docs.
Below are idiomatic Polaris patterns for common UI — layout, forms, data tables, feedback states, modals, and navigation. They're framework-generic; adapt file placement and naming to your own project's conventions.
A typical Polaris React app organizes UI roughly like this — map these to your repo:
<frontend>/pages/<frontend>/components/common/<frontend>/components/<frontend>/modules/<frontend>/hooks/<frontend>/components/loader/Parse $ARGUMENTS to determine the UI task:
| Task | Examples |
|---|---|
| New page | "create a page for X", "add route for X" |
| New component | "create a card/table/form for X" |
| Modify existing | "add a field to X", "update layout of X" |
| Data table | "list of X with filters/search" |
| Form | "settings form for X", "edit form for X" |
| Modal | "confirmation modal", "detail modal for X" |
Before writing code, read the most relevant existing file to understand the surrounding context.
Use the pattern reference below. Always read a real example file before implementing.
<Page
title="Page Title"
backAction={{ onAction: () => navigate("/back") }}
primaryAction={<Button variant="primary">Save</Button>}
>
<BlockStack gap="400">
<Grid>
<Grid.Cell columnSpan={{ xs: 6, sm: 6, md: 4, lg: 8, xl: 8 }}>{/* Main content */}</Grid.Cell>
<Grid.Cell columnSpan={{ xs: 6, sm: 6, md: 2, lg: 4, xl: 4 }}>{/* Sidebar */}</Grid.Cell>
</Grid>
</BlockStack>
</Page>
<Page title="Products">
<BlockStack gap="400">
<Card padding="0">
<IndexTable ... />
</Card>
</BlockStack>
</Page>
<InlineGrid
columns={{ xs: 1, sm: 1, md: 2, lg: 2, xl: 2 }}
gap="400"
>
<CardA />
<CardB />
</InlineGrid>
<BlockStack gap="400">
<Card>
<BlockStack gap="400">
<Text variant="headingMd" as="h5">Section Title</Text>
<Grid>
<Grid.Cell columnSpan={{ xs: 6, sm: 3, md: 3, lg: 6, xl: 6 }}>
<TextField label="Field" ... />
</Grid.Cell>
</Grid>
</BlockStack>
</Card>
</BlockStack>
<Card>
<BlockStack gap="400">
<Text
variant="headingMd"
as="h5"
>
Title
</Text>
{/* content */}
</BlockStack>
</Card>
// Example: a custom <ToggleCard> wrapper component for feature enable/disable cards
<ToggleCard
label="Feature Name"
badgeLabel="Active"
color="success"
btnText="Disable"
onButtonClick={handleToggle}
content="Description of what this feature does."
/>
<Card padding="0">
<IndexTable ... />
</Card>
import { TextField } from "@shopify/polaris";
<TextField
label={t("Field Label")}
value={value}
onChange={setValue}
autoComplete="off"
helpText={t("Optional helper text")}
error={error}
/>;
<Grid>
<Grid.Cell columnSpan={{ xs: 6, sm: 3, md: 3, lg: 6, xl: 6 }}>
<TextField label={t("First Name")} ... />
</Grid.Cell>
<Grid.Cell columnSpan={{ xs: 6, sm: 3, md: 3, lg: 6, xl: 6 }}>
<TextField label={t("Last Name")} ... />
</Grid.Cell>
</Grid>
// Example: a custom <SelectInput> wrapper for all custom dropdowns
<SelectInput
title="Language"
options={[{ label: "English", value: "en" }]}
selected={selected}
onChange={setSelected}
/>
// Example: a custom <ContextualSaveBar> for dirty-state save bars
<ContextualSaveBar
id="unique-form-id"
open={formState.isDirty}
isLoading={isSaving}
onSave={handleSubmit}
onDiscard={() => reset()}
/>
<Card padding="0">
<IndexFilters
queryValue={search}
onQueryChange={setSearch}
onQueryClear={() => setSearch("")}
tabs={tabs}
selected={tabIndex}
filters={filters}
appliedFilters={appliedFilters}
mode={mode}
setMode={setMode}
cancelAction={{ onAction: handleCancel }}
canCreateNewView={false}
/>
<IndexTable
resourceName={{ singular: "item", plural: "items" }}
itemCount={items.length}
selectedItemsCount={allSelected ? "all" : selected.length}
onSelectionChange={handleSelectionChange}
headings={[{ title: t("Name") }, { title: t("Status") }, { title: t("Action") }]}
emptyState={<TableEmptyState />}
>
{items.map((item, index) => (
<IndexTable.Row
id={item.id}
key={item.id}
position={index}
selected={selected.includes(item.id)}
>
<IndexTable.Cell>
<Text>{item.name}</Text>
</IndexTable.Cell>
<IndexTable.Cell>
<Badge tone="success">Active</Badge>
</IndexTable.Cell>
<IndexTable.Cell>
<Button>Edit</Button>
</IndexTable.Cell>
</IndexTable.Row>
))}
</IndexTable>
</Card>
<Card padding="0">
<IndexTable
selectable={false}
headings={[{ title: "Column" }]}
itemCount={items.length}
>
{/* rows */}
</IndexTable>
</Card>
// Use your own skeleton components for loading states
import DummyPageSkeleton from "@/components/loader/DummyPageSkeleton";
import SkeletonLoader from "@/components/loader/SkeletonLoader";
import TableRowsSkeleton from "@/components/loader/TableRowsSkeleton";
// In a page:
if (isLoading) return <DummyPageSkeleton />;
// In a table:
if (isLoading)
return (
<TableRowsSkeleton
rows={10}
columns={4}
/>
);
import { Banner } from "@shopify/polaris";
<Banner tone="warning">{t("Warning message here")}</Banner>;
// Tones: "info" | "success" | "warning" | "critical"
// Use app bridge toast via useToast hook or shopify.toast.show
shopify.toast.show(t("Saved successfully"), { duration: 3000 });
shopify.toast.show(t("Something went wrong"), { isError: true });
import { Badge } from "@shopify/polaris";
<Badge tone="success">Active</Badge>
<Badge tone="warning">Pending</Badge>
<Badge tone="attention">Error</Badge>
<Badge tone="info">Draft</Badge>
// tone: "success" | "info" | "attention" | "warning" | "critical" | "new"
// Example: a custom <EmptyPage> component
<EmptyPage
image="/path/to/image.svg"
heading={t("No items found")}
content={t("Start by adding your first item.")}
primaryAction={<Button variant="primary">{t("Add Item")}</Button>}
insideCard
/>
// Example: a custom <ConfirmationModal> wrapper
<ConfirmationModal
show={showModal}
setOpen={setShowModal}
title={t("Delete Item")}
content={t("Are you sure you want to delete this item? This cannot be undone.")}
primaryActionText={t("Delete")}
primaryAction={handleDelete}
primaryActionIsDestructive
loading={isDeleting}
/>
<Modal
type="app-bridge"
open={show}
setOpen={setShow}
variant="base"
>
<Modal.Section>
<Box padding="400">
<BlockStack gap="400">{/* modal content */}</BlockStack>
</Box>
<Modal.TitleBar title={t("Modal Title")}>
<button
variant="primary"
onClick={handleConfirm}
>
{t("Confirm")}
</button>
<button onClick={() => setShow(false)}>{t("Cancel")}</button>
</Modal.TitleBar>
</Modal.Section>
</Modal>
// Example: a custom <CustomTab> component for tabs
import CustomTab from "@/components/common/CustomTab";
const tabs = [
{ id: "tab-1", content: t("Tab One") },
{ id: "tab-2", content: t("Tab Two"), badge: <Badge tone="attention">3</Badge> },
];
<CustomTab
tabs={tabs}
selected={selectedTab}
onTabClick={setSelectedTab}
fitted
/>;
// Page-level pagination via Page component props:
<Page
pagination={{
hasPrevious: !!pagination?.prev,
onPrevious: () => goToPage(pagination.prev),
hasNext: !!pagination?.next,
onNext: () => goToPage(pagination.next),
}}
>
import { Text } from "@shopify/polaris";
// `variant` controls visual style; `as` controls semantic HTML.
// Choose `as` based on the document outline (only one <h1> per page).
// Main page title (top-level heading)
<Text variant="headingLg" as="h1">Page title (H1)</Text>
// Section within the page
<Text variant="headingLg" as="h2">Section title (H2)</Text>
// Card title inside a section (usually H3 or lower)
<Text variant="headingMd" as="h3">Card title (H3)</Text>
// Visually smaller subsection title, but still part of the outline
<Text variant="headingSm" as="h4">Subsection title (H4)</Text>
// Large body copy
<Text variant="bodyLg" as="p">Large body text</Text>
// Helper / secondary text
<Text variant="bodySm" as="p" tone="subdued">
Helper or secondary text
</Text>
// Inline error text
<Text variant="bodySm" as="span" tone="critical">
Error text
</Text>
| Value | Size | When to Use |
|---|---|---|
"100" | 4px | Tight inline spacing |
"200" | 8px | Between related items |
"300" | 12px | Medium spacing |
"400" | 16px | Standard section spacing (most common) |
"600" | 24px | Between major sections |
"800" | 32px | Large vertical gaps |
"1600" | 64px | Empty state padding |
// Full width on mobile, split on desktop (main/sidebar)
<Grid.Cell columnSpan={{ xs: 6, sm: 6, md: 4, lg: 8, xl: 8 }} /> // Main (8/12)
<Grid.Cell columnSpan={{ xs: 6, sm: 6, md: 2, lg: 4, xl: 4 }} /> // Sidebar (4/12)
// Two equal columns
<Grid.Cell columnSpan={{ xs: 6, sm: 3, md: 3, lg: 6, xl: 6 }} />
// Three equal columns
<Grid.Cell columnSpan={{ xs: 6, sm: 2, md: 2, lg: 4, xl: 4 }} />
// Full width
<Grid.Cell columnSpan={{ xs: 6, sm: 6, md: 6, lg: 12, xl: 12 }} />
t("...") from react-i18next.components/common/, components/loader/), then in feature-specific components under components/ before creating a new component."200", "400", etc.), never CSS pixels.<Card> for grouped content; use <Box> only for minor padding/spacing adjustments.<IndexTable> (not <DataTable>) for all interactive data lists.<Card padding="0">.components/loader/, never raw SkeletonBodyText directly in pages.pnpm run pre-commit after frontend changes.t("...").components/common/.npx claudepluginhub mrmarufpro/polaris-visual-sandbox --plugin polaris-visual-sandboxGuides test-driven development for Django applications using pytest-django, factory_boy, and Django REST Framework. Covers red-green-refactor workflow, conftest fixtures, and coverage reporting.