Agent skill
polaris-ui-patterns
Build consistent UI using Polaris Web Components patterns for common page layouts like index pages, detail pages, forms, and empty states. Use this skill when creating new pages or refactoring existing UI components.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/design/polaris-ui-patterns
SKILL.md
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 */}
<s-section>
<s-card>
<s-table>
<s-table-head>
<s-table-row>
<s-table-cell as="th">Name</s-table-cell>
<s-table-cell as="th">Status</s-table-cell>
<s-table-cell as="th">Created</s-table-cell>
<s-table-cell as="th">Actions</s-table-cell>
</s-table-row>
</s-table-head>
<s-table-body>
{items.map(item => (
<s-table-row key={item.id}>
<s-table-cell>{item.name}</s-table-cell>
<s-table-cell>
<s-badge tone={item.isActive ? "success" : undefined}>
{item.isActive ? "Active" : "Inactive"}
</s-badge>
</s-table-cell>
<s-table-cell>{new Date(item.createdAt).toLocaleDateString()}</s-table-cell>
<s-table-cell>
<s-button-group>
<s-button variant="plain">Edit</s-button>
<s-button variant="plain" tone="critical">Delete</s-button>
</s-button-group>
</s-table-cell>
</s-table-row>
))}
</s-table-body>
</s-table>
</s-card>
</s-section>
</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.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?