Chapter 18: Shopify Polaris UI
Embedded App Architecture
AdPriority runs as an embedded application inside the Shopify Admin. This means the app renders within an iframe managed by Shopify’s App Bridge, inheriting the admin’s navigation, authentication context, and visual language. Merchants never leave the Shopify Admin to use AdPriority.
App Bridge 4.1 Integration
App Bridge 4.1 provides the communication layer between AdPriority’s React frontend and the Shopify Admin host. It handles session token generation, navigation synchronization, and modal management.
EMBEDDED APP ARCHITECTURE
=========================
+---------------------------------------------------------------+
| Shopify Admin (host) |
| |
| +---------------------------+ +----------------------------+ |
| | Admin Navigation | | Top Bar | |
| | - Home | | - Store name | |
| | - Orders | | - Notifications | |
| | - Products | | - Account | |
| | - Customers | +----------------------------+ |
| | - ... | |
| | - Apps | +----------------------------+ |
| | +- AdPriority <--------| App Bridge 4.1 iframe | |
| | |- Dashboard | | | |
| | |- Products | | React + Polaris v13 | |
| | |- Rules | | +----------------------+ | |
| | |- Seasons | | | AdPriority UI | | |
| | |- Settings | | | (renders here) | | |
| +---------------------------+ | +----------------------+ | |
| +----------------------------+ |
+---------------------------------------------------------------+
The entry point initializes App Bridge and wraps the application in the Polaris AppProvider:
// main.tsx
import { AppProvider } from "@shopify/polaris";
import { BrowserRouter } from "react-router-dom";
import enTranslations from "@shopify/polaris/locales/en.json";
import App from "./App";
function Root() {
return (
<AppProvider i18n={enTranslations}>
<BrowserRouter>
<App />
</BrowserRouter>
</AppProvider>
);
}
Session tokens are extracted automatically by App Bridge and attached to every API request. The backend validates these JWTs using the Shopify client secret, confirming the merchant’s identity and shop context without cookies.
Polaris v13 Component Usage
AdPriority uses Polaris v13 exclusively for all UI elements. This guarantees visual consistency with the Shopify Admin and satisfies the App Store review requirement that embedded apps use Polaris.
Core Component Map
| Component | AdPriority Usage |
|---|---|
Page | Top-level page wrapper with title, breadcrumbs, primary action |
Layout | Two-column and single-column content sections |
Card | Content containers for each functional area |
DataTable | Product list with sortable columns |
Badge | Priority level indicators (color-coded 0-5) |
Button | Primary, secondary, and destructive actions |
Modal | Confirmation dialogs for bulk operations and overrides |
Banner | Sync status alerts, errors, onboarding messages |
Select | Priority level dropdown, season picker, filter selectors |
TextField | Search, manual override reason, rule pattern input |
NavigationMenu | App-level navigation (Dashboard, Products, Rules, Seasons, Settings) |
ProgressBar | Sync progress indicator |
Spinner | Loading states for data fetches |
EmptyState | First-run experience when no products or rules exist |
Tabs | Sub-navigation within pages (e.g., product filters) |
IndexTable | Alternative to DataTable for selectable product rows |
Filters | Applied filter tags for product list refinement |
Priority Badge Component
The priority badge is the most frequently used custom component. It maps the 0-5 score to Polaris Badge tones:
PRIORITY BADGE MAPPING
======================
Score Badge Tone Label Color
----- ---------- ----- -----
5 "success" "5 - Push" Green
4 "info" "4 - Strong" Teal
3 "attention" "3 - Normal" Yellow
2 "warning" "2 - Low" Orange
1 "critical" "1 - Minimal" Red (light)
0 "new" "0 - Exclude" Grey
// components/PriorityBadge.tsx
import { Badge } from "@shopify/polaris";
const PRIORITY_CONFIG = {
5: { tone: "success", label: "5 - Push Hard" },
4: { tone: "info", label: "4 - Strong" },
3: { tone: "attention", label: "3 - Normal" },
2: { tone: "warning", label: "2 - Low" },
1: { tone: "critical", label: "1 - Minimal" },
0: { tone: "new", label: "0 - Exclude" },
} as const;
export function PriorityBadge({ priority }: { priority: number }) {
const config = PRIORITY_CONFIG[priority as keyof typeof PRIORITY_CONFIG];
return <Badge tone={config.tone}>{config.label}</Badge>;
}
Key Screens
Screen 1: Dashboard
The dashboard is the landing page after app installation. It provides a high-level overview of the priority distribution across the product catalog, the last sync status, and quick action buttons.
+------------------------------------------------------------------+
| AdPriority [Sync Now]|
+------------------------------------------------------------------+
| |
| +---------------------------+ +-------------------------------+ |
| | Priority Distribution | | Sync Status | |
| | | | | |
| | [PIE CHART] | | Shopify: 2 min ago [ok] | |
| | | | Sheet: 1 hour ago [ok] | |
| | 5 - Push: 312 13% | | GMC: Daily at 2AM [ok] | |
| | 4 - Strong: 487 20% | | | |
| | 3 - Normal: 824 34% | | Next scheduled sync: | |
| | 2 - Low: 401 17% | | Today at 3:00 PM | |
| | 1 - Minimal: 198 8% | | | |
| | 0 - Exclude: 203 8% | +-------------------------------+ |
| | | |
| +---------------------------+ +-------------------------------+ |
| | Quick Stats | |
| +---------------------------+ | | |
| | Recent Activity | | Total Products: 2,425 | |
| | | | Active in GMC: 15,284 | |
| | 10:30 - Season changed | | Needs Attention: 47 | |
| | to Winter | | New Arrivals: 12 | |
| | 10:28 - 47 products | | Rules Active: 20 | |
| | updated via rules | | | |
| | 09:15 - Sync completed | | [View Products] | |
| | (2,425 ok, 0 errors) | | [Manage Rules] | |
| | | +-------------------------------+ |
| +---------------------------+ |
+------------------------------------------------------------------+
Layout: Layout with two-column Layout.Section pairs. The pie chart uses Chart.js rendered inside a Card. Each stat card uses Polaris Text components with variant="headingLg" for the numbers.
Quick Actions:
- “Sync Now” button (primary action on the
Pagecomponent) - “View Products” link navigates to the Products screen
- “Manage Rules” link navigates to the Rules screen
“Needs Attention” count highlights products with priority 0 that still have inventory, meaning they could be advertised but are currently excluded.
Screen 2: Products List
The products list is the primary working screen. It displays every active product with its current priority score, category group, seasonal context, and sync status.
+------------------------------------------------------------------+
| Products [Recalculate All] [...]|
+------------------------------------------------------------------+
| Filters: [Priority: All v] [Type: All v] [Status: All v] [Search]|
| Applied: Priority >= 3 x | Type: Outerwear x |
+------------------------------------------------------------------+
| [ ] Product | Type | Vendor | Priority |
| Title | | | Score |
|------------------------------------------------------------------+
| [ ] Jordan Craig | Stacked Jeans | Jordan Craig| [5-Push] |
| Stacked Jeans | | | seasonal |
|------------------------------------------------------------------+
| [ ] Rebel Minds Puffer | Puffer Jackets | Rebel Minds | [5-Push] |
| Jacket Camo | | | seasonal |
|------------------------------------------------------------------+
| [ ] New Era Yankees | Baseball-Fitted| New Era | [4-Strong]|
| 59FIFTY Navy | | | rule+tag |
|------------------------------------------------------------------+
| [ ] Ethika Men Go | Men-Underwear | Ethika | [2-Low] |
| Pac Go | | | default |
|------------------------------------------------------------------+
| [ ] G3 Patriots | Hoodies & | G-III Sports| [0-Excl] |
| Waffled Hoodie | Sweatshirts | | tag:DEAD50|
+------------------------------------------------------------------+
| Showing 1-50 of 2,425 [<] [1] [2] [3] ... [49] [>] |
+------------------------------------------------------------------+
| Selected: 3 products [Set Priority...] [Recalculate] [Export] |
+------------------------------------------------------------------+
Columns:
| Column | Source | Sortable |
|---|---|---|
| Product (title + image thumbnail) | Shopify product data | Yes (alphabetical) |
| Type | product_type field | Yes |
| Vendor | vendor field | Yes |
| Priority | Calculated score with PriorityBadge | Yes (numeric) |
| Season | Current season label | No |
| Status | sync_status (synced/pending/error) | Yes |
| Actions | Edit button, overflow menu | No |
Filters (using Polaris Filters component):
- Priority: Dropdown with 0-5, or “All”
- Type: Dropdown populated from category groups (T-Shirts, Jeans & Pants, etc.)
- Status: Synced, Pending, Error
- Search: Free-text search across title, vendor, SKU
Bulk Actions (appear when rows are selected via IndexTable):
- “Set Priority” – Opens modal with priority selector and reason field
- “Recalculate” – Re-runs the scoring engine for selected products
- “Export” – Downloads selected products as CSV
Pagination: Server-side, 50 products per page. Uses cursor-based pagination for performance with large catalogs.
Screen 3: Product Detail
Clicking a product row opens the detail view. This screen shows the full priority calculation breakdown: what the base score is, which modifiers applied, and the resulting final score.
+------------------------------------------------------------------+
| < Back to Products |
| Jordan Craig Stacked Jeans [Save] [...]|
+------------------------------------------------------------------+
| |
| +---------------------------+ +-------------------------------+ |
| | Product Info | | Priority Breakdown | |
| | | | | |
| | Title: Jordan Craig | | Category: Jeans & Pants | |
| | Stacked Jeans | | Base priority: 4 | |
| | Type: Men-Bottoms- | | | |
| | Stacked Jeans | | Seasonal (Winter): 4 | |
| | Vendor: Jordan Craig | | | |
| | Tags: jordan-craig, | | Tag: NAME BRAND +1 | |
| | NAME BRAND, in-stock | | Tag: in-stock +1 | |
| | Created: 2025-09-15 | | (capped at 5) | |
| | Variants: 12 | | | |
| | Inventory: 48 total | | ================================| |
| | | | Final Priority: [5-Push] | |
| +---------------------------+ | Source: seasonal + tags | |
| | | |
| +---------------------------+ | [ ] Lock this priority | |
| | Manual Override | +-------------------------------+ |
| | | |
| | Priority: [5 v] | +-------------------------------+ |
| | Reason: [____________] | | Custom Labels (GMC) | |
| | [Apply Override] | | | |
| | | | label_0: priority-5 | |
| | Note: Overrides all | | label_1: winter | |
| | rules until unlocked. | | label_2: jeans-pants | |
| +---------------------------+ | label_3: in-stock | |
| | label_4: name-brand | |
| | | |
| | Last synced: 2 hours ago | |
| +-------------------------------+ |
+------------------------------------------------------------------+
Priority Breakdown Card: Shows each step of the calculation pipeline in order. The merchant can see exactly why a product received its score, which eliminates the “black box” concern.
Manual Override Card: Contains a Select dropdown (0-5), a TextField for the reason (required when overriding), and an “Apply Override” Button. When applied, the product’s priority_locked flag is set to true, and no automated rules will change the score until the lock is removed.
Custom Labels Card: Displays the exact values that will be (or have been) synced to Google Merchant Center. This provides transparency into what Google Ads campaigns will see.
Screen 4: Rules Engine
The rules engine screen lists all category rules and provides a form for creating and editing rules. Rules define the base priority for product type patterns and their seasonal modifiers.
+------------------------------------------------------------------+
| Rules [Add Rule] |
+------------------------------------------------------------------+
| |
| +--------------------------------------------------------------+ |
| | Active Rules (20) [Reorder]| |
| |--------------------------------------------------------------| |
| | # | Name | Pattern | Base | Products | |
| |----|--------------------|---------—--------|------|----------| |
| | 1 | Outerwear - Heavy | *Puffer*, | 5 | 35 | |
| | | | *Shearling* | | | |
| | 2 | Hoodies & Sweats | *Hoodies*, | 3 | 264 | |
| | | | *Sweatshirts* | | | |
| | 3 | T-Shirts | Men-Tops-T-Shirt*| 3 | 1,363 | |
| | 4 | Jeans & Pants | *Pants-Jeans, | 4 | 911 | |
| | | | *Stacked Jeans | | | |
| | 5 | Shorts | *Shorts* | 3 | 315 | |
| | ... | |
| | 20 | Exclude | Bath & Body, | 0 | 50 | |
| | | | Gift Cards, ... | | | |
| +--------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
Create / Edit Rule Form (opens in a modal or inline section):
+------------------------------------------------------------------+
| Create Rule |
+------------------------------------------------------------------+
| |
| Rule Name: [Outerwear - Heavy______________] |
| Description: [Winter outerwear, peak demand___] |
| |
| Product Type Pattern: |
| [*Puffer Jackets, *Shearling___________________] |
| (comma-separated, supports * wildcard) |
| |
| Base Priority: [5 - Push Hard v] |
| |
| Seasonal Modifiers: |
| +-------------+----------+ |
| | Season | Priority | |
| |-------------|----------| |
| | Winter | [5 v] | |
| | Spring | [1 v] | |
| | Summer | [0 v] | |
| | Fall | [4 v] | |
| +-------------+----------+ |
| |
| [ ] Active |
| |
| Matching Products: 35 (preview) |
| |
| [Cancel] [Save Rule] |
+------------------------------------------------------------------+
The pattern field supports comma-separated product type strings with * wildcard matching. When the merchant types a pattern, a live preview shows how many products match. This provides immediate feedback before saving.
Rules are evaluated in order (drag-to-reorder supported). The first matching rule wins, so more specific rules should be placed higher in the list.
Screen 5: Seasonal Calendar
The seasonal calendar provides a visual representation of season boundaries across the year, with a matrix editor for category-season priority assignments.
+------------------------------------------------------------------+
| Seasons [Add Season]|
+------------------------------------------------------------------+
| |
| +--------------------------------------------------------------+ |
| | Calendar View | |
| | | |
| | Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec | |
| | [====WINTER====][=====SPRING=====][=====SUMMER=====][==FALL==]| |
| | Dec 1 - Feb 28 Mar 1 - May 31 Jun 1 - Aug 31 Sep1-Nov30 | |
| | | |
| | Drag boundaries to adjust dates | |
| +--------------------------------------------------------------+ |
| |
| +--------------------------------------------------------------+ |
| | Category x Season Matrix [Edit Mode] | |
| |--------------------------------------------------------------| |
| | Category | Winter | Spring | Summer | Fall | Default | |
| |-------------------|--------|--------|--------|------|---------| |
| | T-Shirts | 2 | 4 | 5 | 3 | 3 | |
| | Jeans & Pants | 4 | 4 | 3 | 5 | 4 | |
| | Shorts | 0 | 3 | 5 | 1 | 3 | |
| | Hoodies & Sweats | 5 | 3 | 1 | 5 | 3 | |
| | Outerwear - Heavy | 5 | 1 | 0 | 4 | 3 | |
| | Outerwear - Medium| 4 | 3 | 0 | 4 | 3 | |
| | Outerwear - Light | 2 | 4 | 1 | 3 | 3 | |
| | Headwear - Caps | 3 | 3 | 3 | 3 | 3 | |
| | Headwear - Cold | 5 | 1 | 0 | 3 | 2 | |
| | Headwear - Summer | 0 | 3 | 4 | 2 | 2 | |
| | Joggers | 4 | 3 | 2 | 4 | 3 | |
| | Underwear & Socks | 2 | 2 | 2 | 2 | 2 | |
| | Accessories | 2 | 2 | 2 | 2 | 2 | |
| | Footwear - Sandals| 0 | 2 | 5 | 0 | 2 | |
| | Footwear - Shoes | 3 | 3 | 3 | 3 | 3 | |
| | Swim Shorts | 0 | 2 | 5 | 0 | 2 | |
| | Women - Apparel | 2 | 3 | 3 | 2 | 2 | |
| | Exclude | 0 | 0 | 0 | 0 | 0 | |
| +--------------------------------------------------------------+ |
| |
| Current Season: Winter (Dec 1 - Feb 28) |
| Next Transition: Spring on Mar 1 (19 days) |
| |
+------------------------------------------------------------------+
Calendar View: A horizontal bar chart showing the four season blocks across 12 months. In edit mode, the season boundaries become draggable handles. Adjusting a boundary updates the seasons table dates and triggers a recalculation preview showing how many products would change priority.
Category x Season Matrix: A grid where each cell is a Select dropdown (0-5). Cells are color-coded to match the priority badge scheme (green for 5, grey for 0). Editing a cell updates the corresponding season_rules record.
Transition Countdown: Displayed below the matrix, showing the current active season and days until the next automatic transition. When a transition is imminent (within 7 days), a Banner with tone warning appears at the top of the page.
Screen 6: Settings
The settings screen manages GMC connection details, sync configuration, and notification preferences.
+------------------------------------------------------------------+
| Settings [Save] |
+------------------------------------------------------------------+
| |
| +--------------------------------------------------------------+ |
| | Google Merchant Center | |
| | | |
| | Connection Status: Connected [checkmark] | |
| | Merchant ID: [123456789________] | |
| | Sync Method: [Google Sheets v] | |
| | Sheet URL: [https://docs.google.com/spreadsheets/d/...] | |
| | | |
| | [Test Connection] [Disconnect] | |
| +--------------------------------------------------------------+ |
| |
| +--------------------------------------------------------------+ |
| | Sync Schedule | |
| | | |
| | Frequency: [Every 6 hours v] | |
| | Options: Every hour / Every 6 hours / Daily / Manual | |
| | | |
| | Auto-sync on product changes: [x] Enabled | |
| | Auto-sync on season transition: [x] Enabled | |
| +--------------------------------------------------------------+ |
| |
| +--------------------------------------------------------------+ |
| | Priority Defaults | |
| | | |
| | Default priority for unmapped types: [3 - Normal v] | |
| | New arrival boost: [x] Enabled | |
| | New arrival duration: [14] days | |
| | New arrival priority: [5 - Push Hard v] | |
| +--------------------------------------------------------------+ |
| |
| +--------------------------------------------------------------+ |
| | Notifications | |
| | | |
| | Email on sync failure: [x] Enabled | |
| | Email on season transition: [x] Enabled | |
| | Notification email: [will@nexusclothing.com] | |
| +--------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
GMC Connection Card: The Google Sheets URL is the primary configuration. A “Test Connection” button verifies the Sheets API can write to the specified spreadsheet. For Pro tier merchants using the Content API directly, this card shows OAuth connection status instead.
Sync Schedule Card: Controls how often AdPriority pushes updated labels to the Google Sheet. The frequency dropdown determines the cron interval for the background worker job.
Priority Defaults Card: Sets the fallback priority for products whose product type does not match any rule, and configures the new arrival boost behavior.
Screen 7: Billing
The billing screen shows the current subscription plan, usage against limits, and upgrade/downgrade options. It integrates with the Shopify Billing API.
+------------------------------------------------------------------+
| Billing |
+------------------------------------------------------------------+
| |
| +--------------------------------------------------------------+ |
| | Current Plan: Growth ($79/mo) | |
| | | |
| | Status: Active | |
| | Billing: Monthly (via Shopify) | |
| | Next bill: March 10, 2026 | |
| | Products: 2,425 / Unlimited | |
| | | |
| | Features included: | |
| | [ok] Unlimited products | |
| | [ok] Seasonal automation | |
| | [ok] Rules engine | |
| | [ok] Tag modifiers | |
| | [ok] New arrival boost | |
| | [--] Google Ads integration (Pro) | |
| | [--] AI recommendations (Pro) | |
| | [--] ROAS tracking (Pro) | |
| +--------------------------------------------------------------+ |
| |
| +-------------------+ +-------------------+ +-----------------+ |
| | Starter $29/mo | | Growth $79/mo | | Pro $199/mo | |
| | | | (current) | | | |
| | 500 products | | Unlimited | | Unlimited | |
| | Manual scoring | | Seasonal auto | | Google Ads API | |
| | GMC sync | | Rules engine | | AI suggestions | |
| | | | Tag modifiers | | ROAS tracking | |
| | | | | | Performance | |
| | | | | | dashboard | |
| | [Downgrade] | | Current Plan | | [Upgrade] | |
| +-------------------+ +-------------------+ +-----------------+ |
| |
+------------------------------------------------------------------+
Upgrade and downgrade actions redirect to the Shopify billing confirmation page via the appSubscriptionCreate GraphQL mutation. Shopify handles payment collection, proration, and cancellation natively.
Navigation
AdPriority uses the Polaris NavigationMenu component (provided by App Bridge) to register its navigation items with the Shopify Admin sidebar. This places the app’s pages directly in the admin navigation when the merchant is inside the app.
APP NAVIGATION STRUCTURE
========================
AdPriority
|
+-- Dashboard / Overview, stats, quick actions
+-- Products /products Product list, priorities, bulk ops
+-- Rules /rules Category rules, rule builder
+-- Seasons /seasons Seasonal calendar, matrix editor
+-- Settings /settings GMC config, sync, notifications
// App.tsx
import { NavMenu } from "@shopify/app-bridge-react";
import { Routes, Route } from "react-router-dom";
export default function App() {
return (
<>
<NavMenu>
<a href="/" rel="home">Dashboard</a>
<a href="/products">Products</a>
<a href="/rules">Rules</a>
<a href="/seasons">Seasons</a>
<a href="/settings">Settings</a>
</NavMenu>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/rules" element={<Rules />} />
<Route path="/seasons" element={<Seasons />} />
<Route path="/settings" element={<Settings />} />
<Route path="/billing" element={<Billing />} />
</Routes>
</>
);
}
The billing page is intentionally omitted from the main navigation since merchants access it infrequently. A link to it is placed in the Settings page and in the Dashboard’s plan status card.
Responsive Design and Accessibility
Polaris v13 handles responsive layout automatically. All Polaris components adapt to the embedded iframe width, which varies based on the merchant’s browser window size. The Layout component switches from two-column to single-column at narrow widths.
Accessibility is built into Polaris at the component level, meeting WCAG 2.0 AA standards. AdPriority adds the following custom accessibility considerations:
| Consideration | Implementation |
|---|---|
| Priority colors | Never rely on color alone; badge text always includes the numeric score |
| Data tables | Use proper th scope and aria-sort attributes via Polaris DataTable |
| Modals | Focus trap and keyboard navigation handled by Polaris Modal |
| Loading states | aria-busy on containers during data fetch, Spinner with label text |
| Error messages | Banner with tone="critical" and descriptive text, linked to the failed field |
State Management
AdPriority uses React Query (TanStack Query) for server state and React context for local UI state. There is no global client-side store (no Redux, no Zustand). The rationale is that AdPriority’s state is almost entirely server-derived: product data, rules, seasons, and sync status all originate from the backend API.
STATE MANAGEMENT PATTERN
========================
React Query (Server State) React Context (UI State)
+---------------------------+ +-------------------------+
| useProducts() | | FilterContext |
| - product list | | - selected priority |
| - pagination cursor | | - selected type |
| - refetch on mutation | | - search query |
| | | |
| useRules() | | SelectionContext |
| - rule list | | - selected row IDs |
| - create/update/delete | | - bulk action state |
| | | |
| useSeasons() | | ToastContext |
| - season config | | - success/error msgs |
| - matrix data | | |
| | | |
| useSyncStatus() | | |
| - polling every 30s | | |
+---------------------------+ +-------------------------+
React Query’s stale-while-revalidate pattern ensures the UI always shows data immediately from cache while fetching fresh data in the background. Mutations (priority changes, rule edits) optimistically update the cache and roll back on server error.