← Shopify App BuilderCONTENT HISTORY

Update to Shopify App Builder

Snapshot Sep 30, 2026 · 23:13 UTC · version 1.4.1

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Use this skill for Polaris 12.x UI Components. Triggers include: 'polaris components', 'react ui shopify', 'approvider initialization', 'polaris form', 'polaris button', 'polaris card', 'polaris table indexTable', 'polaris modal dialog', 'polaris select dropdown', 'polaris textfield input', 'polaris checkbox radio', 'polaris navigation', 'polaris layout blocklist inlinestack', 'polaris design tokens', 'polaris icons', 'polaris stack grid', 'polaris page frame resource list', 'polaris loading spinner', 'polaris toast notification', 'polaris banner alert', 'shopify ui component library', 'polaris 12', 'polaris css import', 'polaris styled components', 'polaris form validation', 'polaris accessibility a11y', 'polaris icon reference', 'polaris theme provider', 'polaris page actions buttons'.",
  "included_files": [],
  "name": "polaris-ui",
  "skill_md_contents": "---\nname: polaris-ui\ndescription: \"Use this skill for Polaris 12.x UI Components. Triggers include: 'polaris components', 'react ui shopify', 'approvider initialization', 'polaris form', 'polaris button', 'polaris card', 'polaris table indexTable', 'polaris modal dialog', 'polaris select dropdown', 'polaris textfield input', 'polaris checkbox radio', 'polaris navigation', 'polaris layout blocklist inlinestack', 'polaris design tokens', 'polaris icons', 'polaris stack grid', 'polaris page frame resource list', 'polaris loading spinner', 'polaris toast notification', 'polaris banner alert', 'shopify ui component library', 'polaris 12', 'polaris css import', 'polaris styled components', 'polaris form validation', 'polaris accessibility a11y', 'polaris icon reference', 'polaris theme provider', 'polaris page actions buttons'.\"\n---\n\n# Polaris 12.x UI Components\n\nPolaris 12.x is Shopify's official React component library for building consistent, accessible admin and embedded app UIs. This skill covers Polaris component patterns, AppProvider initialization, 25+ commonly-used components, design tokens, form validation, and real-world layout patterns for Shopify apps.\n\n## When Asked\n\n**When asked to build a Shopify app UI with forms and inputs:**\nProvide a complete React component example using AppProvider, TextField, Select, Checkbox, and Button components with proper state management and form submission handlers.\n\n**When asked about Polaris layout and spacing:**\nExplain BlockStack, InlineStack, and InlineGrid with code examples showing proper spacing, padding, and responsive behavior using gap and padding props.\n\n**When asked to create data tables or resource lists:**\nUse IndexTable for tabular data with sorting, selection, and bulk actions; ResourceList for browseable item collections with filters and search.\n\n**When asked about Polaris design tokens or theming:**\nProvide color, typography, and spacing token values; explain how to access tokens via CSS variables and customize with custom properties.\n\n**When asked to implement forms with validation:**\nBuild controlled forms with TextField, Select, Checkbox components; add error states, help text, and form submission with validation logic.\n\n**When asked about Polaris icons and iconography:**\nReference available icons (Icon component, 150+ icons from @shopify/polaris-icons), show Icon usage with color and size props.\n\n**When asked to create modals, dialogs, or overlays:**\nUse Modal component for full-screen overlays, Popover for inline popups, and ContextualSaveBar for unsaved changes UI patterns.\n\n---\n\n## Polaris 12.x Architecture Overview\n\nPolaris provides:\n- **AppProvider**: Required root component that initializes Polaris context (i18n, theme, CSRF token)\n- **25+ UI Components**: Button, Card, Layout (BlockStack, InlineStack, InlineGrid), TextField, Select, Modal, IndexTable, ResourceList, etc.\n- **Design Tokens**: Color (primary, success, warning, critical), typography (display, heading, body), spacing (0.5rem, 1rem, 1.5rem, 2rem), shadow, border-radius\n- **Polaris Icons**: 150+ SVG icons via @shopify/polaris-icons; Icon component wrapper\n- **Accessibility**: WCAG 2.1 AA compliant, semantic HTML, ARIA labels, keyboard navigation\n- **CSS Imports**: Import from @shopify/polaris (includes Tailwind-like utility classes; use CSS modules or styled-components for scoping)\n\n### Installation & Setup\n\n```bash\nnpm install @shopify/polaris @shopify/polaris-icons\n# or yarn / pnpm\n```\n\n### AppProvider Initialization (Required)\n\n```tsx\nimport React from 'react';\nimport { AppProvider } from '@shopify/polaris';\nimport '@shopify/polaris/build/esm/styles.css';\n\nfunction App() {\n  return (\n    <AppProvider i18n={{}}>\n      <YourComponent />\n    </AppProvider>\n  );\n}\n\nexport default App;\n```\n\n**Props:**\n- `i18n`: Object with translation strings (optional; defaults to English)\n- `features`: Object to enable experimental features\n- `colorScheme`: Light (default) or dark mode\n- `link`: Function for custom link navigation\n\n---\n\n## Core Layout Components\n\n### BlockStack (Vertical Stack)\n\n```tsx\nimport { BlockStack, Text } from '@shopify/polaris';\n\nexport function VerticalLayout() {\n  return (\n    <BlockStack gap=\"400\">\n      <Text as=\"h1\">Title</Text>\n      <Text as=\"p\">Content item 1</Text>\n      <Text as=\"p\">Content item 2</Text>\n    </BlockStack>\n  );\n}\n```\n\n**Props:**\n- `gap`: \"100\" | \"200\" | \"300\" | \"400\" | \"500\" (controls vertical spacing; default \"200\")\n- `align`: \"start\" | \"center\" | \"end\"\n- `inlineAlign`: \"start\" | \"center\" | \"end\"\n- `children`: React elements\n\n### InlineStack (Horizontal Stack)\n\n```tsx\nimport { InlineStack, Button } from '@shopify/polaris';\n\nexport function HorizontalLayout() {\n  return (\n    <InlineStack gap=\"400\" wrap={false}>\n      <Button>Save</Button>\n      <Button>Cancel</Button>\n    </InlineStack>\n  );\n}\n```\n\n**Props:**\n- `gap`: \"100\" | \"200\" | \"300\" | \"400\" | \"500\"\n- `align`: \"start\" | \"center\" | \"end\" | \"space-between\" | \"space-around\"\n- `wrap`: boolean (default true)\n- `blockAlign`: \"start\" | \"center\" | \"end\" | \"baseline\"\n\n### InlineGrid (Responsive Grid)\n\n```tsx\nimport { InlineGrid, Card } from '@shopify/polaris';\n\nexport function ResponsiveGrid() {\n  return (\n    <InlineGrid columns={['oneThird', 'twoThirds']} gap=\"400\">\n      <Card title=\"Sidebar\">Filters</Card>\n      <Card title=\"Main\">Content</Card>\n    </InlineGrid>\n  );\n}\n```\n\n**Props:**\n- `columns`: string[] (e.g., [\"oneHalf\", \"oneHalf\"], [\"oneThird\", \"twoThirds\"], [\"full\"]; responsive)\n- `gap`: \"100\" | \"200\" | \"300\" | \"400\" | \"500\"\n\n### Box (Generic Container)\n\n```tsx\nimport { Box } from '@shopify/polaris';\n\nexport function PaddedBox() {\n  return (\n    <Box padding=\"400\" background=\"bg-surface-secondary\">\n      Content with padding\n    </Box>\n  );\n}\n```\n\n**Props:**\n- `padding`: \"0\" | \"100\" | \"200\" | \"300\" | \"400\" | \"500\"\n- `paddingBlock`: vertical padding\n- `paddingInline`: horizontal padding\n- `background`: \"bg-surface\" | \"bg-surface-secondary\" | \"bg-fill\" | \"bg-fill-selected\"\n- `border`: \"divider\"\n- `borderRadius`: \"base\" | \"large\"\n\n---\n\n## Form Components\n\n### TextField (Text Input)\n\n```tsx\nimport { TextField } from '@shopify/polaris';\nimport { useState } from 'react';\n\nexport function FormInput() {\n  const [value, setValue] = useState('');\n  const [error, setError] = useState('');\n\n  const handleChange = (value) => {\n    setValue(value);\n    if (value.length < 3) {\n      setError('Minimum 3 characters');\n    } else {\n      setError('');\n    }\n  };\n\n  return (\n    <TextField\n      label=\"Product Name\"\n      value={value}\n      onChange={handleChange}\n      error={error}\n      helpText=\"Enter the product name (3+ chars)\"\n      placeholder=\"e.g., Awesome Widget\"\n      requiredIndicator\n    />\n  );\n}\n```\n\n**Props:**\n- `label`: string\n- `value`: string (controlled component)\n- `onChange`: (value: string) => void\n- `error`: string | true (displays error message or red border)\n- `helpText`: string (gray text below input)\n- `placeholder`: string\n- `type`: \"text\" | \"email\" | \"password\" | \"tel\" | \"url\" | \"number\" (default \"text\")\n- `disabled`: boolean\n- `readOnly`: boolean\n- `requiredIndicator`: boolean\n- `maxLength`: number\n- `prefix`: ReactNode (text/icon before input)\n- `suffix`: ReactNode (text/icon after input)\n\n### Select (Dropdown)\n\n```tsx\nimport { Select } from '@shopify/polaris';\nimport { useState } from 'react';\n\nexport function DropdownSelect() {\n  const [selected, setSelected] = useState('option1');\n\n  return (\n    <Select\n      label=\"Status\"\n      options={[\n        { label: 'Active', value: 'active' },\n        { label: 'Inactive', value: 'inactive' },\n        { label: 'Archived', value: 'archived' },\n      ]}\n      value={selected}\n      onChange={setSelected}\n    />\n  );\n}\n```\n\n**Props:**\n- `label`: string\n- `options`: { label: string; value: string }[]\n- `value`: string (controlled)\n- `onChange`: (value: string) => void\n- `disabled`: boolean\n- `placeholder`: string\n\n### Checkbox & RadioButton\n\n```tsx\nimport { Checkbox, RadioButton, BlockStack } from '@shopify/polaris';\nimport { useState } from 'react';\n\nexport function CheckboxExample() {\n  const [checked, setChecked] = useState(false);\n\n  return (\n    <BlockStack gap=\"200\">\n      <Checkbox\n        label=\"I agree to terms\"\n        checked={checked}\n        onChange={() => setChecked(!checked)}\n        helpText=\"Read our terms before enabling\"\n      />\n    </BlockStack>\n  );\n}\n```\n\n**Props:**\n- `label`: string\n- `checked`: boolean\n- `onChange`: (checked: boolean) => void\n- `disabled`: boolean\n- `error`: boolean | string\n\n### Form Component (Wrapper)\n\n```tsx\nimport { Form, FormLayout, TextField, Select, Button } from '@shopify/polaris';\nimport { useState } from 'react';\n\nexport function AppForm() {\n  const [formData, setFormData] = useState({ name: '', category: '' });\n  const [errors, setErrors] = useState({});\n\n  const handleSubmit = (e) => {\n    e.preventDefault();\n    if (!formData.name) {\n      setErrors({ name: 'Required' });\n      return;\n    }\n    console.log('Submit:', formData);\n  };\n\n  return (\n    <Form onSubmit={handleSubmit}>\n      <FormLayout>\n        <TextField\n          label=\"Product Name\"\n          value={formData.name}\n          onChange={(value) => setFormData({ ...formData, name: value })}\n          error={errors.name}\n          requiredIndicator\n        />\n        <Select\n          label=\"Category\"\n          options={[\n            { label: 'Electronics', value: 'electronics' },\n            { label: 'Clothing', value: 'clothing' },\n          ]}\n          value={formData.category}\n          onChange={(value) => setFormData({ ...formData, category: value })}\n        />\n        <Button submit>Save Product</Button>\n      </FormLayout>\n    </Form>\n  );\n}\n```\n\n---\n\n## Data Display Components\n\n### Card\n\n```tsx\nimport { Card, Text, BlockStack } from '@shopify/polaris';\n\nexport function CardExample() {\n  return (\n    <Card title=\"Sales This Quarter\" sectioned>\n      <BlockStack gap=\"200\">\n        <Text as=\"p\">$10,250 (↑ 12% from last quarter)</Text>\n      </BlockStack>\n    </Card>\n  );\n}\n```\n\n**Props:**\n- `title`: string | ReactNode\n- `subtitle`: string\n- `sectioned`: boolean (adds padding)\n- `padding`: \"0\" | \"200\" | \"400\"\n- `background`: \"bg-surface\" | \"bg-fill\"\n- `children`: ReactNode\n\n### IndexTable (Data Table)\n\n```tsx\nimport {\n  IndexTable,\n  useIndexResourceState,\n  Button,\n  Text,\n} from '@shopify/polaris';\nimport { useState } from 'react';\n\nexport function DataTable() {\n  const products = [\n    { id: '1', name: 'Widget A', price: '$29.99', status: 'Active' },\n    { id: '2', name: 'Widget B', price: '$39.99', status: 'Draft' },\n  ];\n\n  const { selectedResources, allResourcesSelected, handleSelectionChange } =\n    useIndexResourceState(products);\n\n  return (\n    <IndexTable\n      resourceName={{ singular: 'product', plural: 'products' }}\n      itemCount={products.length}\n      selectedItemsCount={\n        allResourcesSelected ? 'All' : selectedResources.length\n      }\n      onSelectionChange={handleSelectionChange}\n      headings={[\n        { title: 'Name' },\n        { title: 'Price' },\n        { title: 'Status' },\n      ]}\n    >\n      {products.map((product) => (\n        <IndexTable.Row\n          key={product.id}\n          id={product.id}\n          selected={selectedResources.includes(product.id)}\n        >\n          <IndexTable.Cell>{product.name}</IndexTable.Cell>\n          <IndexTable.Cell>{product.price}</IndexTable.Cell>\n          <IndexTable.Cell>{product.status}</IndexTable.Cell>\n        </IndexTable.Row>\n      ))}\n    </IndexTable>\n  );\n}\n```\n\n**Key Props:**\n- `resourceName`: { singular: string; plural: string }\n- `itemCount`: number (total items)\n- `selectedItemsCount`: number | \"All\"\n- `onSelectionChange`: (selected) => void\n- `headings`: { title: string }[]\n\n### ResourceList\n\n```tsx\nimport { ResourceList, ResourceItem, Text, Button } from '@shopify/polaris';\n\nexport function ListExample() {\n  const items = [\n    { id: '1', name: 'Product A', status: 'Active' },\n    { id: '2', name: 'Product B', status: 'Draft' },\n  ];\n\n  return (\n    <ResourceList\n      resourceName={{ singular: 'product', plural: 'products' }}\n      items={items}\n      renderItem={(item) => (\n        <ResourceItem\n          id={item.id}\n          accessibilityLabel={`View product ${item.name}`}\n        >\n          <Text variant=\"bodyMd\" as=\"span\">\n            {item.name}\n          </Text>\n          <Text variant=\"bodySm\" as=\"span\" tone=\"subdued\">\n            {item.status}\n          </Text>\n        </ResourceItem>\n      )}\n    />\n  );\n}\n```\n\n---\n\n## Modal & Overlay Components\n\n### Modal\n\n```tsx\nimport { Modal, Button, TextField, BlockStack } from '@shopify/polaris';\nimport { useState } from 'react';\n\nexport function ModalExample() {\n  const [isOpen, setIsOpen] = useState(false);\n\n  return (\n    <>\n      <Button onClick={() => setIsOpen(true)}>Open Modal</Button>\n      <Modal\n        open={isOpen}\n        onClose={() => setIsOpen(false)}\n        title=\"Create Product\"\n        primaryAction={{\n          content: 'Save',\n          onAction: () => {\n            console.log('Save clicked');\n            setIsOpen(false);\n          },\n        }}\n        secondaryActions={[\n          {\n            content: 'Cancel',\n            onAction: () => setIsOpen(false),\n          },\n        ]}\n      >\n        <Modal.Section>\n          <BlockStack gap=\"400\">\n            <TextField label=\"Product Name\" />\n          </BlockStack>\n        </Modal.Section>\n      </Modal>\n    </>\n  );\n}\n```\n\n**Props:**\n- `open`: boolean\n- `onClose`: () => void\n- `title`: string | ReactNode\n- `primaryAction`: { content: string; onAction: () => void; loading?: boolean; disabled?: boolean }\n- `secondaryActions`: same shape as primaryAction (array)\n- `children`: ReactNode (Modal.Section wrapper recommended)\n\n### ContextualSaveBar (Sticky Footer)\n\n```tsx\nimport { ContextualSaveBar, Button } from '@shopify/polaris';\nimport { useState } from 'react';\n\nexport function UnsavedChangesBar() {\n  const [isDirty, setIsDirty] = useState(false);\n\n  return (\n    <>\n      {isDirty && (\n        <ContextualSaveBar\n          message=\"Unsaved changes\"\n          saveAction={{\n            onAction: () => {\n              console.log('Save');\n              setIsDirty(false);\n            },\n            loading: false,\n            disabled: false,\n          }}\n          discardAction={{\n            onAction: () => {\n              console.log('Discard');\n              setIsDirty(false);\n            },\n          }}\n        />\n      )}\n      <Button onClick={() => setIsDirty(true)}>Make Change</Button>\n    </>\n  );\n}\n```\n\n---\n\n## Navigation & Routing\n\n### Navigation Component\n\n```tsx\nimport { Navigation } from '@shopify/polaris';\nimport { HomeIcon, ProductIcon, OrdersIcon } from '@shopify/polaris-icons';\n\nexport function AppNavigation() {\n  return (\n    <Navigation location=\"/\">\n      <Navigation.Section\n        items={[\n          {\n            url: '/',\n            label: 'Dashboard',\n            icon: HomeIcon,\n          },\n          {\n            url: '/products',\n            label: 'Products',\n            icon: ProductIcon,\n          },\n          {\n            url: '/orders',\n            label: 'Orders',\n            icon: OrdersIcon,\n          },\n        ]}\n      />\n    </Navigation>\n  );\n}\n```\n\n**Props:**\n- `location`: string (current URL for active indicator)\n- `children`: Navigation.Section (groups of items)\n- Navigation.Section.items[]: { url: string; label: string; icon?: React.ComponentType; badge?: string }\n\n---\n\n## Icons & Iconography\n\n### Using Polaris Icons\n\n```tsx\nimport { Icon, Button, InlineStack } from '@shopify/polaris';\nimport {\n  SaveIcon,\n  DeleteIcon,\n  SearchIcon,\n  PlusIcon,\n} from '@shopify/polaris-icons';\n\nexport function IconExamples() {\n  return (\n    <InlineStack gap=\"400\">\n      <Button icon={SaveIcon}>Save</Button>\n      <Button icon={DeleteIcon} variant=\"primary\">\n        Delete\n      </Button>\n      <Icon source={SearchIcon} tone=\"base\" />\n    </InlineStack>\n  );\n}\n```\n\n**Common Icons:**\n- SaveIcon, CancelIcon, DeleteIcon, EditIcon\n- SearchIcon, FilterIcon, SortIcon\n- PlusIcon, MinusIcon, ChevronDownIcon, ChevronRightIcon\n- HomeIcon, ProductIcon, OrdersIcon, CustomerIcon\n- CheckIcon, AlertIcon, InfoIcon, WarningIcon\n\n**Icon Component Props:**\n- `source`: React.ComponentType (the icon SVG)\n- `tone`: \"base\" | \"subdued\" | \"positive\" | \"warning\" | \"critical\"\n- `accessibilityLabel`: string\n\n---\n\n## Design Tokens & Theming\n\n### Color Tokens\n\n```\nPrimary: var(--p-color-interactive)\nSuccess: var(--p-color-text-success)\nWarning: var(--p-color-text-warning)\nCritical: var(--p-color-text-critical)\nText: var(--p-color-text), var(--p-color-text-subdued)\nSurface: var(--p-color-bg-surface), var(--p-color-bg-surface-secondary)\n```\n\n### Spacing Values\n\n```\n100: 0.25rem (4px)\n200: 0.5rem (8px)\n300: 1rem (16px)\n400: 1.5rem (24px)\n500: 2rem (32px)\n600: 2.5rem (40px)\n700: 3rem (48px)\n```\n\n### Typography\n\n```\nDisplay: font-size 2rem, font-weight 600 (headings)\nHeading: font-size 1.5rem, font-weight 600 (titles)\nBodyLg: font-size 1rem, font-weight 400 (body text)\nBodyMd: font-size 0.875rem, font-weight 400 (default body)\nBodySm: font-size 0.8125rem, font-weight 400 (secondary text)\nCode: monospace, 0.875rem\n```\n\n### Using in Custom CSS\n\n```css\n.custom-box {\n  background: var(--p-color-bg-surface);\n  color: var(--p-color-text);\n  padding: var(--p-space-300);\n  border-radius: var(--p-border-radius-base);\n}\n```\n\n---\n\n## Worked Example: Product Management App\n\n```tsx\nimport React, { useState } from 'react';\nimport {\n  AppProvider,\n  Page,\n  Layout,\n  Card,\n  IndexTable,\n  Button,\n  Modal,\n  TextField,\n  FormLayout,\n  InlineStack,\n  useIndexResourceState,\n  BlockStack,\n  Text,\n} from '@shopify/polaris';\nimport { PlusIcon } from '@shopify/polaris-icons';\nimport '@shopify/polaris/build/esm/styles.css';\n\nexport function ProductApp() {\n  const [products, setProducts] = useState([\n    { id: '1', name: 'Widget A', price: '$29.99', status: 'Active' },\n    { id: '2', name: 'Widget B', price: '$39.99', status: 'Draft' },\n  ]);\n\n  const [isModalOpen, setIsModalOpen] = useState(false);\n  const [newProduct, setNewProduct] = useState({ name: '', price: '' });\n  const [errors, setErrors] = useState({});\n\n  const { selectedResources, handleSelectionChange } =\n    useIndexResourceState(products);\n\n  const handleAddProduct = () => {\n    setErrors({});\n    if (!newProduct.name) {\n      setErrors({ name: 'Product name is required' });\n      return;\n    }\n    setProducts([\n      ...products,\n      {\n        id: String(Date.now()),\n        name: newProduct.name,\n        price: newProduct.price || '$0.00',\n        status: 'Draft',\n      },\n    ]);\n    setNewProduct({ name: '', price: '' });\n    setIsModalOpen(false);\n  };\n\n  const handleDelete = () => {\n    setProducts(\n      products.filter((p) => !selectedResources.includes(p.id))\n    );\n  };\n\n  return (\n    <AppProvider i18n={{}}>\n      <Page\n        title=\"Products\"\n        subtitle=\"Manage your product catalog\"\n        primaryAction={{\n          content: 'Add Product',\n          icon: PlusIcon,\n          onAction: () => setIsModalOpen(true),\n        }}\n      >\n        <Layout>\n          <Layout.Section>\n            <Card>\n              {selectedResources.length > 0 && (\n                <Card.Section>\n                  <InlineStack gap=\"400\">\n                    <Text as=\"p\" tone=\"subdued\">\n                      {selectedResources.length} selected\n                    </Text>\n                    <Button\n                      variant=\"primary\"\n                      tone=\"critical\"\n                      onClick={handleDelete}\n                    >\n                      Delete\n                    </Button>\n                  </InlineStack>\n                </Card.Section>\n              )}\n              <IndexTable\n                resourceName={{ singular: 'product', plural: 'products' }}\n                itemCount={products.length}\n                selectedItemsCount={selectedResources.length}\n                onSelectionChange={handleSelectionChange}\n                headings={[\n                  { title: 'Name' },\n                  { title: 'Price' },\n                  { title: 'Status' },\n                ]}\n              >\n                {products.map((product) => (\n                  <IndexTable.Row\n                    key={product.id}\n                    id={product.id}\n                    selected={selectedResources.includes(product.id)}\n                  >\n                    <IndexTable.Cell>{product.name}</IndexTable.Cell>\n                    <IndexTable.Cell>{product.price}</IndexTable.Cell>\n                    <IndexTable.Cell>{product.status}</IndexTable.Cell>\n                  </IndexTable.Row>\n                ))}\n              </IndexTable>\n            </Card>\n          </Layout.Section>\n        </Layout>\n\n        <Modal\n          open={isModalOpen}\n          onClose={() => setIsModalOpen(false)}\n          title=\"Add Product\"\n          primaryAction={{\n            content: 'Save',\n            onAction: handleAddProduct,\n          }}\n          secondaryActions={[\n            {\n              content: 'Cancel',\n              onAction: () => setIsModalOpen(false),\n            },\n          ]}\n        >\n          <Modal.Section>\n            <FormLayout>\n              <TextField\n                label=\"Product Name\"\n                value={newProduct.name}\n                onChange={(value) =>\n                  setNewProduct({ ...newProduct, name: value })\n                }\n                error={errors.name}\n                requiredIndicator\n              />\n              <TextField\n                label=\"Price\"\n                value={newProduct.price}\n                onChange={(value) =>\n                  setNewProduct({ ...newProduct, price: value })\n                }\n                prefix=\"$\"\n              />\n            </FormLayout>\n          </Modal.Section>\n        </Modal>\n      </Page>\n    </AppProvider>\n  );\n}\n```\n\n---\n\n## Accessibility Best Practices\n\n- Always provide semantic `label` props to form inputs\n- Use `requiredIndicator` for required fields\n- Provide `helpText` to clarify input expectations\n- Use descriptive button labels; avoid \"Click here\"\n- Set `accessibilityLabel` on icon-only buttons\n- Use `ResourceItem.accessibilityLabel` for screen readers\n- Use `tone` prop on Text and Icon for semantic color meaning, not just visual\n\n---\n\n## Common Patterns Checklist\n\n- [ ] Wrap app in `<AppProvider>` with CSS import\n- [ ] Use `BlockStack` for vertical layouts, `InlineStack` for horizontal\n- [ ] Use `Card` to group related content\n- [ ] Use `IndexTable` for tabular data, `ResourceList` for browseable items\n- [ ] Use `Modal` for overlays; `ContextualSaveBar` for unsaved changes\n- [ ] Import icons from `@shopify/polaris-icons`\n- [ ] Validate form inputs and show `error` prop on invalid fields\n- [ ] Use `formLayout` inside `<Form>` for proper spacing\n- [ ] Use design token CSS variables for colors and spacing\n- [ ] Test keyboard navigation (Tab, Enter, Escape) in all interactive components\n"
}

SHA-256 of public snapshot: 00f40bdda467ab289f2a70ca5c792866405c90a271d36b0e4f3c67011463f499