polaris-ui-patterns

Polaris UI Patterns Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "polaris-ui-patterns" with this command: npx skills add sarojpunde/shopify-dev-toolkit-claude-plugins/sarojpunde-shopify-dev-toolkit-claude-plugins-polaris-ui-patterns

Polaris UI Patterns Skill

Purpose

This skill provides reusable UI patterns and templates for common page layouts in Shopify apps using Polaris Web Components.

When to Use This Skill

  • Creating new pages (index, detail, form)

  • Implementing common UI patterns

  • Building consistent layouts

  • Adding empty states

  • Creating modals and forms

  • Implementing tables with actions

Core Patterns

Pattern 1: Index/List Page

Use for: Products, Orders, Customers, Custom Entities

import type { LoaderFunctionArgs } from "react-router"; import { useLoaderData } from "react-router"; import { authenticate } from "../shopify.server"; import { db } from "../db.server"; import { json } from "@remix-run/node";

export const loader = async ({ request }: LoaderFunctionArgs) => { const { session } = await authenticate.admin(request); const shop = await db.shop.findUnique({ where: { shopDomain: session.shop } });

const items = await db.item.findMany({ where: { shopId: shop.id }, orderBy: { createdAt: 'desc' }, take: 50, });

const stats = { total: items.length, active: items.filter(i => i.isActive).length, };

return json({ items, stats }); };

export default function ItemsIndexPage() { const { items, stats } = useLoaderData<typeof loader>();

return ( <s-page heading="Items"> {/* Stats Section */} <s-section> <s-grid columns="3"> <s-box border="base" borderRadius="base" padding="400"> <s-stack gap="200" direction="vertical"> <s-text variant="headingMd" as="h3">Total Items</s-text> <s-text variant="heading2xl" as="p">{stats.total}</s-text> </s-stack> </s-box> <s-box border="base" borderRadius="base" padding="400"> <s-stack gap="200" direction="vertical"> <s-text variant="headingMd" as="h3">Active</s-text> <s-text variant="heading2xl" as="p">{stats.active}</s-text> </s-stack> </s-box> </s-grid> </s-section>

  {/* Table Section */}
  &#x3C;s-section>
    &#x3C;s-card>
      &#x3C;s-table>
        &#x3C;s-table-head>
          &#x3C;s-table-row>
            &#x3C;s-table-cell as="th">Name&#x3C;/s-table-cell>
            &#x3C;s-table-cell as="th">Status&#x3C;/s-table-cell>
            &#x3C;s-table-cell as="th">Created&#x3C;/s-table-cell>
            &#x3C;s-table-cell as="th">Actions&#x3C;/s-table-cell>
          &#x3C;/s-table-row>
        &#x3C;/s-table-head>
        &#x3C;s-table-body>
          {items.map(item => (
            &#x3C;s-table-row key={item.id}>
              &#x3C;s-table-cell>{item.name}&#x3C;/s-table-cell>
              &#x3C;s-table-cell>
                &#x3C;s-badge tone={item.isActive ? "success" : undefined}>
                  {item.isActive ? "Active" : "Inactive"}
                &#x3C;/s-badge>
              &#x3C;/s-table-cell>
              &#x3C;s-table-cell>{new Date(item.createdAt).toLocaleDateString()}&#x3C;/s-table-cell>
              &#x3C;s-table-cell>
                &#x3C;s-button-group>
                  &#x3C;s-button variant="plain">Edit&#x3C;/s-button>
                  &#x3C;s-button variant="plain" tone="critical">Delete&#x3C;/s-button>
                &#x3C;/s-button-group>
              &#x3C;/s-table-cell>
            &#x3C;/s-table-row>
          ))}
        &#x3C;/s-table-body>
      &#x3C;/s-table>
    &#x3C;/s-card>
  &#x3C;/s-section>
&#x3C;/s-page>

); }

Pattern 2: Detail/Edit Page

export const loader = async ({ params, request }: LoaderFunctionArgs) => { const { session } = await authenticate.admin(request); const shop = await db.shop.findUnique({ where: { shopDomain: session.shop } });

const item = await db.item.findUnique({ where: { id: params.id, shopId: shop.id, }, });

if (!item) { throw new Response("Not Found", { status: 404 }); }

return json({ item }); };

export const action = async ({ params, request }: ActionFunctionArgs) => { const formData = await request.formData(); const name = formData.get("name"); const description = formData.get("description");

await db.item.update({ where: { id: params.id }, data: { name, description }, });

return redirect("/app/items"); };

export default function ItemDetailPage() { const { item } = useLoaderData<typeof loader>();

return ( <s-page heading={item.name} backUrl="/app/items"> <form method="post"> <s-card> <s-stack gap="400" direction="vertical"> <s-text-field label="Name" name="name" defaultValue={item.name} required /> <s-text-field label="Description" name="description" defaultValue={item.description} multiline={4} /> <s-button-group> <s-button type="submit" variant="primary">Save</s-button> <s-button url="/app/items">Cancel</s-button> </s-button-group> </s-stack> </s-card> </form> </s-page> ); }

Pattern 3: Modal Pattern

function ItemModal({ item, onClose }) { const submit = useSubmit();

function handleSubmit(event) { event.preventDefault(); const formData = new FormData(event.target); submit(formData, { method: "post" }); onClose(); }

return ( <s-modal open onClose={onClose} title="Edit Item"> <form onSubmit={handleSubmit}> <s-modal-section> <s-stack gap="400" direction="vertical"> <s-text-field label="Name" name="name" defaultValue={item?.name} /> <s-text-field label="Description" name="description" defaultValue={item?.description} multiline={3} /> </s-stack> </s-modal-section> <s-modal-footer> <s-button-group> <s-button onClick={onClose}>Cancel</s-button> <s-button type="submit" variant="primary">Save</s-button> </s-button-group> </s-modal-footer> </form> </s-modal> ); }

Pattern 4: Empty State

{items.length === 0 ? ( <s-card> <s-empty-state heading="No items yet" image="https://cdn.shopify.com/..." > <s-text variant="bodyMd"> Start by adding your first item </s-text> <s-button variant="primary" url="/app/items/new"> Add Item </s-button> </s-empty-state> </s-card> ) : ( // Item list )}

Pattern 5: Loading State

import { useNavigation } from "@remix-run/react";

export default function ItemsPage() { const navigation = useNavigation(); const isLoading = navigation.state === "loading";

return ( <s-page heading="Items"> {isLoading ? ( <s-card> <s-stack gap="400" direction="vertical"> <s-skeleton-display-text /> <s-skeleton-display-text /> <s-skeleton-display-text /> </s-stack> </s-card> ) : ( // Content )} </s-page> ); }

Pattern 6: Form with Validation

export const action = async ({ request }: ActionFunctionArgs) => { const formData = await request.formData(); const name = formData.get("name");

const errors = {}; if (!name) errors.name = "Name is required"; if (name.length < 3) errors.name = "Name must be at least 3 characters";

if (Object.keys(errors).length > 0) { return json({ errors }, { status: 400 }); }

await db.item.create({ data: { name } }); return redirect("/app/items"); };

export default function NewItemPage() { const actionData = useActionData<typeof action>();

return ( <form method="post"> <s-text-field label="Name" name="name" error={actionData?.errors?.name} required /> <s-button type="submit" variant="primary">Save</s-button> </form> ); }

Best Practices

  • Consistent Layouts - Use the same page structure across the app

  • Loading States - Always show skeleton loaders during data fetching

  • Empty States - Provide clear guidance when no data exists

  • Error Handling - Show user-friendly error messages

  • Form Validation - Validate on submit, show inline errors

  • Responsive Design - Test on mobile, tablet, and desktop

  • Accessibility - Use semantic HTML and ARIA attributes

  • SSR Compatibility - Avoid hydration mismatches

  • Performance - Lazy load components when appropriate

  • User Feedback - Show success/error toasts after actions

Remember: Consistent UI patterns create a professional, predictable user experience.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

polaris-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

shopify workflow & tools

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

shopify-api-patterns

No summary provided by upstream source.

Repository SourceNeeds Review