{"id":17069,"plugin_id":"plugins_6a701c7b1f9481919cf7c7448ddc1bd4","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:13:56.625Z","digest":"4a5dba46defb64e08e4f8d13b57aab853694231d119861f624d3f9cde312fb43","against":null,"payload":{"description":"Use when you want a Shopify embedded app to feel modern, fast, and opinionated like Linear, Notion, Vercel, or Cron — speed-first, keyboard-first, calm UI, opinionated defaults, no-config success path. Covers keyboard shortcuts inside App Bridge, command palette patterns, micro-interactions Polaris allows, density vs spacious tradeoffs, brand expression within Polaris tokens, and 15 concrete patterns to copy from modern SaaS into Polaris-compliant Shopify apps. Triggers: 'modern shopify app', 'fast app', 'linear-style ux', 'notion-style ux', 'keyboard shortcuts shopify app', 'command palette', 'calm ui', 'minimalist polaris', 'opinionated defaults', 'fewer settings', 'speed first'.","included_files":[],"name":"ux-modern-app-feel","skill_md_contents":"---\nname: ux-modern-app-feel\ndescription: \"Use when you want a Shopify embedded app to feel modern, fast, and opinionated like Linear, Notion, Vercel, or Cron — speed-first, keyboard-first, calm UI, opinionated defaults, no-config success path. Covers keyboard shortcuts inside App Bridge, command palette patterns, micro-interactions Polaris allows, density vs spacious tradeoffs, brand expression within Polaris tokens, and 15 concrete patterns to copy from modern SaaS into Polaris-compliant Shopify apps. Triggers: 'modern shopify app', 'fast app', 'linear-style ux', 'notion-style ux', 'keyboard shortcuts shopify app', 'command palette', 'calm ui', 'minimalist polaris', 'opinionated defaults', 'fewer settings', 'speed first'.\"\n---\n\n# Modern App Feel for Shopify Embedded Apps\n\nMost Shopify apps feel like 2015 — wall-of-settings, slow page loads, mouse-only, no keyboard, no taste. The modern reference is Linear, Notion, Vercel, Cron, Raycast, Superhuman, Arc — apps that feel calm, instant, opinionated, and keyboard-first. This skill is how to bring that feel inside Polaris and App Bridge 4.x without breaking the Built for Shopify (BFS) badge or merchant expectations.\n\nThe premise: Polaris is the floor, not the ceiling. You can absolutely build a Linear-tier embedded app within Polaris — you just have to be deliberate about the seven things that actually make an app feel modern, and ruthless about everything else.\n\n---\n\n## When to Use This Skill\n\nUse when:\n- Building a new Shopify embedded app and you want it to feel categorically better than the competitor apps in the same category\n- Refactoring an existing app that feels slow, cluttered, or \"Shopify-default\" with no taste\n- Asked \"how do I make this feel more like Linear / Notion / Vercel inside a Shopify app\"\n- Adding keyboard shortcuts, a command palette, optimistic UI, or other speed-feel patterns to an embedded app\n- Deciding between dense vs spacious layouts for a merchant-facing dashboard\n- Asked about brand expression — when can you override Polaris colors, and when shouldn't you\n- About to ship an \"infinite settings\" page and want the heuristic for whether each setting earns its place\n- Considering a custom design system on top of Polaris (almost always wrong — this skill explains why)\n\nDo NOT use when:\n- Building a checkout extension or storefront block — those have their own constraints (Checkout UI Extensions, theme app extensions)\n- Building a non-embedded surface (POS, mobile app shell)\n- The merchant has explicitly asked for a Shopify-default-looking app for trust reasons (rare, but real for some legal/finance apps)\n\n---\n\n## The Six Modern SaaS UX Principles (And How Each Lands Inside Polaris)\n\nEvery modern SaaS app worth copying — Linear, Notion, Vercel, Cron, Raycast, Superhuman, Arc — converges on these six principles. The trick is translating each into something Polaris-compliant.\n\n### 1. Speed Above Everything\n\nLinear's mantra is \"fast is a feature.\" Anything under 100ms feels instant, anything over 300ms feels slow, anything over 1s loses the user. Modern apps engineer the perceived performance budget aggressively — optimistic UI updates, prefetching, route-level caching, skeleton screens that match real layout.\n\n**Inside Polaris:** Polaris doesn't fight you here. It ships fast-rendering primitives. The bottlenecks are usually (a) your Remix loaders waiting on Shopify GraphQL, (b) re-renders from unmemoized state, (c) no optimistic updates on mutations. Polaris does NOT ship a built-in optimistic UI helper — you wire it yourself with Remix `useFetcher` or TanStack Query.\n\n### 2. Density (When the Merchant Is Power)\n\nLinear, Notion, Airtable use **information-dense** layouts. More rows, smaller padding, tighter typography. The merchant who lives in your app 4 hours a day wants more data per pixel, not more whitespace.\n\n**Inside Polaris:** Polaris spacing tokens default to spacious (gap `400` = 24px). For power-user surfaces, drop to `200` or `300`. `IndexTable` is denser than `ResourceList`. Use `Text variant=\"bodySm\"` (13px) for table cells. Polaris allows this — just stay consistent within a surface.\n\n### 3. Keyboard-First\n\nLinear is famous for: `C` to create, `/` to search, `Cmd+K` to command palette, `?` to show all shortcuts. Every action is reachable without the mouse. Superhuman built a $30/month email business on this principle alone.\n\n**Inside Polaris:** Polaris/App Bridge 4.x has no first-class shortcut API. You add shortcuts yourself with `react-hotkeys-hook` or `Mousetrap`, scoped to the embedded iframe. App Bridge does intercept `Cmd+S` for its save bar — respect that. Otherwise the keyboard is yours.\n\n### 4. Opinionated Defaults\n\nNotion ships with sensible defaults for almost everything. The new doc is named, the cover image is reasonable, the database has 3 useful columns. Compare to Jira: 47 fields, none filled in, you do all the work.\n\n**Inside Polaris:** Pre-fill every form field with the best guess. Pick the most common option in dropdowns. Auto-detect the merchant's brand color from theme. Skip the \"configure first\" step entirely when possible. Polaris `TextField` accepts a `value` — use it.\n\n### 5. No-Config Success Path\n\nCron, Linear, and Arc all open with you already at first value. No questionnaire. No empty setup wizard. PageFly's \"What do you want to build?\" picker is the Shopify-specific version of this — no blank canvas, ever.\n\n**Inside Polaris:** Use the App Bridge install handshake to seed everything you need (OAuth grants scopes, you read the shop, you fetch a few products). The first screen is the product working, not a checklist of things to do.\n\n### 6. Calm UI\n\nVercel and Linear are quiet. Almost monochrome. One accent color. Generous typography hierarchy. No drop shadows on every card. No animated gradients. No 8 different button styles. The visual noise floor is near-zero, so real content stands out.\n\n**Inside Polaris:** Polaris is already calm — that's its strength. The mistake is OVER-decorating to \"stand out\" — gradient backgrounds, custom icons, full-bleed hero images. Resist. Polaris's restraint is a gift; lean in. Pick one accent (`--p-color-bg-fill-brand`), use it sparingly, and let the content speak.\n\n---\n\n## Speed: The \"Feels Instant\" Rule\n\n### The Latency Budget\n\n| Latency | User perception |\n|---|---|\n| 0–100ms | Feels instant (target this) |\n| 100–300ms | Feels responsive |\n| 300ms–1s | Noticeable delay |\n| 1s+ | Lost the user |\n\nEvery interaction in your app should land under 100ms perceived latency. Real network latency from Shopify GraphQL is usually 200–800ms — so perceived speed comes from optimistic UI, not faster network.\n\n### Optimistic UI in Remix\n\n```tsx\nimport { useFetcher } from '@remix-run/react';\nimport { useState } from 'react';\n\nexport function ToggleSwitch({ id, initialEnabled }) {\n  const fetcher = useFetcher();\n  const [optimistic, setOptimistic] = useState(initialEnabled);\n\n  const enabled = fetcher.formData\n    ? fetcher.formData.get('enabled') === 'true'\n    : optimistic;\n\n  const handleToggle = () => {\n    const next = !enabled;\n    setOptimistic(next);\n    fetcher.submit(\n      { id, enabled: String(next) },\n      { method: 'POST', action: '/api/toggle' }\n    );\n  };\n\n  return <Checkbox checked={enabled} onChange={handleToggle} />;\n}\n```\n\nThe checkbox flips instantly. The network request happens in the background. If it fails, you revert with a toast.\n\n### Prefetch on Intent (Remix)\n\n```tsx\nimport { Link } from '@remix-run/react';\n\n<Link to=\"/products/123\" prefetch=\"intent\">\n  View product\n</Link>\n```\n\n`prefetch=\"intent\"` triggers the loader on hover/focus — by the time the merchant clicks, the page is already loaded. Use this on every nav link. It costs almost nothing and makes navigation feel teleportational.\n\n### Skeleton Screens That Match Real Layout\n\nPolaris ships `SkeletonBodyText`, `SkeletonDisplayText`, `SkeletonThumbnail`. Use them, but match the real layout dimensions. A skeleton that resizes when content loads is worse than a slightly slow content load — the layout shift is the jarring bit.\n\n```tsx\n{isLoading ? (\n  <SkeletonBodyText lines={3} />\n) : (\n  <Text as=\"p\">{data.description}</Text>\n)}\n```\n\n### What NOT to Optimize\n\n- Don't add loading spinners under 300ms — they make the app feel slower, not faster\n- Don't animate everything — every animation over 200ms is friction\n- Don't cache aggressively across shops — stale data is worse than slow data in B2B\n\n---\n\n## Keyboard Shortcuts Inside App Bridge\n\nApp Bridge 4.x reserves a small set of system shortcuts (Cmd+S for save bar, Escape for modals). Everything else is yours.\n\n### Recommended Library: react-hotkeys-hook\n\n```bash\nnpm install react-hotkeys-hook\n```\n\n```tsx\nimport { useHotkeys } from 'react-hotkeys-hook';\n\nexport function ProductsPage() {\n  const navigate = useNavigate();\n  const [paletteOpen, setPaletteOpen] = useState(false);\n\n  useHotkeys('mod+k', (e) => {\n    e.preventDefault();\n    setPaletteOpen(true);\n  });\n\n  useHotkeys('c', () => {\n    navigate('/products/new');\n  }, { enableOnFormTags: false });\n\n  useHotkeys('/', (e) => {\n    e.preventDefault();\n    document.getElementById('search-input')?.focus();\n  });\n\n  useHotkeys('shift+?', () => {\n    setShortcutsHelpOpen(true);\n  });\n\n  return <Page>...</Page>;\n}\n```\n\n`mod+k` translates to Cmd+K on Mac, Ctrl+K on Windows/Linux — never hardcode `meta` or `ctrl`.\n\n### Suggested Shortcut Set (Linear-Style)\n\n| Key | Action |\n|---|---|\n| `Cmd+K` | Open command palette |\n| `/` | Focus search |\n| `C` | Create new (primary entity for current page) |\n| `G` then `D` | Go to Dashboard |\n| `G` then `P` | Go to Products |\n| `G` then `O` | Go to Orders |\n| `E` | Edit selected row |\n| `Shift+?` | Show shortcuts help |\n| `Esc` | Close modal/palette |\n\n### Accessibility Considerations\n\n1. **Never bind unmodified letters globally without checking focus.** Use `enableOnFormTags: false` so `C` doesn't fire while the merchant is typing a product name.\n2. **Always provide a visible alternative.** Every shortcut must map to a visible button or menu item. A keyboard-only feature is an accessibility failure.\n3. **Show the shortcut next to the button.** Use Polaris `KeyboardKey` component or a small `<kbd>` tag: `Save ⌘S`.\n4. **Don't override system shortcuts.** Cmd+T, Cmd+W, Cmd+R, Cmd+L belong to the browser. Cmd+S belongs to App Bridge.\n5. **Respect `prefers-reduced-motion`.** If the user has reduced motion on, skip the palette open animation.\n\n### The \"?\" Help Dialog\n\nModern apps treat `?` as \"show all shortcuts.\" Build a Polaris `Modal` listing every shortcut, grouped by section. Open it on `Shift+?`. Make it the discovery surface for everything keyboard.\n\n```tsx\n<Modal open={helpOpen} onClose={() => setHelpOpen(false)} title=\"Keyboard shortcuts\">\n  <Modal.Section>\n    <BlockStack gap=\"400\">\n      <Text variant=\"headingSm\">Navigation</Text>\n      <InlineStack gap=\"400\">\n        <Text>Go to Dashboard</Text>\n        <kbd>G</kbd> <kbd>D</kbd>\n      </InlineStack>\n      ...\n    </BlockStack>\n  </Modal.Section>\n</Modal>\n```\n\n---\n\n## Command Palette Inside Polaris\n\nThe command palette is the single highest-leverage modern UX pattern. It collapses navigation, search, and actions into one keyboard-driven surface.\n\n### Option A: Polaris Modal + Combobox (Quick, BFS-Safe)\n\nThe easiest implementation uses Polaris primitives directly — `Modal` as the container, `Combobox` as the search-with-results.\n\n```tsx\nimport { Modal, Combobox, Listbox, Icon } from '@shopify/polaris';\nimport { SearchIcon } from '@shopify/polaris-icons';\n\nexport function CommandPalette({ open, onClose }) {\n  const [query, setQuery] = useState('');\n\n  const commands = [\n    { id: 'nav-dashboard', label: 'Go to Dashboard', shortcut: 'G D', action: () => navigate('/') },\n    { id: 'nav-products', label: 'Go to Products', shortcut: 'G P', action: () => navigate('/products') },\n    { id: 'create-product', label: 'Create product', shortcut: 'C', action: () => navigate('/products/new') },\n    { id: 'create-discount', label: 'Create discount', action: () => navigate('/discounts/new') },\n    { id: 'search-orders', label: 'Search orders', action: () => navigate('/orders?focus=search') },\n  ];\n\n  const filtered = commands.filter(c =>\n    c.label.toLowerCase().includes(query.toLowerCase())\n  );\n\n  return (\n    <Modal open={open} onClose={onClose} title=\"\" small>\n      <Modal.Section>\n        <Combobox\n          activator={\n            <Combobox.TextField\n              prefix={<Icon source={SearchIcon} />}\n              onChange={setQuery}\n              value={query}\n              placeholder=\"Type a command or search...\"\n              autoComplete=\"off\"\n              autoFocus\n            />\n          }\n        >\n          <Listbox onSelect={(id) => {\n            const cmd = commands.find(c => c.id === id);\n            cmd?.action();\n            onClose();\n          }}>\n            {filtered.map(cmd => (\n              <Listbox.Option key={cmd.id} value={cmd.id}>\n                {cmd.label}{cmd.shortcut && ` — ${cmd.shortcut}`}\n              </Listbox.Option>\n            ))}\n          </Listbox>\n        </Combobox>\n      </Modal.Section>\n    </Modal>\n  );\n}\n```\n\nThis is good. Not as fast as a real command palette (Modal has open animation, Combobox has its own focus model), but it ships in an afternoon and stays inside Polaris.\n\n### Option B: Custom Palette With cmdk (Linear-Tier)\n\nFor a Linear/Raycast-feel palette, use [cmdk](https://cmdk.paco.me/) by Paco Coursey — the same library powering Linear, Vercel, and Raycast palettes. Headless, accessible, fuzzy search built in.\n\n```bash\nnpm install cmdk\n```\n\n```tsx\nimport { Command } from 'cmdk';\nimport '@shopify/polaris/build/esm/styles.css';\n\nexport function FastPalette({ open, onClose }) {\n  return (\n    <Command.Dialog open={open} onOpenChange={onClose} label=\"Command palette\">\n      <Command.Input placeholder=\"Type a command...\" />\n      <Command.List>\n        <Command.Empty>No results found.</Command.Empty>\n\n        <Command.Group heading=\"Navigation\">\n          <Command.Item onSelect={() => navigate('/products')}>\n            Products\n            <kbd>G P</kbd>\n          </Command.Item>\n        </Command.Group>\n\n        <Command.Group heading=\"Actions\">\n          <Command.Item onSelect={() => navigate('/products/new')}>\n            Create product\n            <kbd>C</kbd>\n          </Command.Item>\n        </Command.Group>\n      </Command.List>\n    </Command.Dialog>\n  );\n}\n```\n\nYou must then style cmdk to match Polaris — use Polaris tokens (`var(--p-color-bg-surface)`, `var(--p-color-text)`, `var(--p-border-radius-200)`) for the wrapper, input, items. The result feels like Linear AND looks like Shopify.\n\n### Palette Rules\n\n1. **Open from Cmd+K, close from Esc.** No exceptions.\n2. **Fuzzy match, not exact.** \"crt prd\" should find \"Create product.\" cmdk does this natively.\n3. **Group commands by section.** Navigation, Actions, Search results. Linear does this; Notion does this.\n4. **Show shortcuts next to each command.** Teaches users the keybinds passively.\n5. **Recents first when query is empty.** Surface the 3 most-used commands at the top of an empty palette.\n6. **Keep it under 8 visible items.** More is overwhelming.\n7. **No animation on open.** Or under 100ms fade-in. Anything slower breaks the speed feel.\n\n---\n\n## Density vs Spacious\n\nA common mistake: assume Polaris's default spacious feel suits every surface. It doesn't. Pick density per surface based on the merchant's relationship with that surface.\n\n### Use Spacious (gap 400+, padding 400+, bodyMd text) When:\n\n- The merchant visits this surface rarely (settings, billing, account)\n- This is an onboarding or first-time-use surface\n- The merchant is making an irreversible decision (delete, upgrade, archive)\n- Mobile-first surface where touch targets need 44px+\n- Marketing-feeling surfaces (welcome, what's new)\n\n### Use Dense (gap 200, padding 200, bodySm text, IndexTable not ResourceList) When:\n\n- The merchant lives here daily (dashboard, orders list, products list)\n- This is a power-user view with bulk actions\n- The user is comparing many rows or columns\n- Desktop-only or desktop-primary surface (most embedded admin apps)\n- The merchant has expressed they want \"more on screen\" (common in 1-star reviews)\n\n### Spacious Default — Reference\n\n```tsx\n<Page title=\"Settings\">\n  <Layout>\n    <Layout.Section>\n      <BlockStack gap=\"400\">\n        <Card>\n          <BlockStack gap=\"400\">\n            <Text variant=\"headingMd\">Brand</Text>\n            <TextField label=\"Store name\" />\n          </BlockStack>\n        </Card>\n      </BlockStack>\n    </Layout.Section>\n  </Layout>\n</Page>\n```\n\n### Dense Power-User — Reference\n\n```tsx\n<Page title=\"Orders\" fullWidth>\n  <Card padding=\"200\">\n    <IndexTable\n      condensed\n      resourceName={{ singular: 'order', plural: 'orders' }}\n      itemCount={orders.length}\n      headings={[\n        { title: 'Order' },\n        { title: 'Date' },\n        { title: 'Customer' },\n        { title: 'Total' },\n        { title: 'Status' },\n      ]}\n    >\n      {orders.map(order => (\n        <IndexTable.Row key={order.id} id={order.id}>\n          <IndexTable.Cell>\n            <Text variant=\"bodySm\" fontWeight=\"medium\">{order.name}</Text>\n          </IndexTable.Cell>\n          <IndexTable.Cell>\n            <Text variant=\"bodySm\" tone=\"subdued\">{order.date}</Text>\n          </IndexTable.Cell>\n          ...\n        </IndexTable.Row>\n      ))}\n    </IndexTable>\n  </Card>\n</Page>\n```\n\n`IndexTable` with `condensed` + `padding=\"200\"` + `bodySm` cells gets you ~40% more rows on screen vs default Polaris.\n\n---\n\n## Brand Expression Within Polaris\n\nPolaris allows brand expression in a few sanctioned ways. Use these; don't go further or you risk BFS rejection.\n\n### Sanctioned Customization\n\n1. **Override `--p-color-bg-fill-brand` and friends.** Polaris exposes brand color tokens as CSS variables. Override at the app root.\n\n```css\n:root {\n  --p-color-bg-fill-brand: #6E56CF;\n  --p-color-bg-fill-brand-hover: #7C66D9;\n  --p-color-bg-fill-brand-active: #5A45B5;\n  --p-color-text-brand-on-bg-fill: #FFFFFF;\n}\n```\n\nThis recolors primary buttons, selected states, and brand accents while leaving everything else Polaris-default. Safe, tasteful, BFS-compliant.\n\n2. **Custom logo / icon in title bar.** Use App Bridge `<ui-title-bar>` slot for a small brand mark next to the page title.\n\n3. **Branded empty states.** Polaris `EmptyState` accepts a custom `image` prop. Use it for a single tasteful illustration per surface.\n\n4. **One accent color, used sparingly.** Pick one accent, apply it to maybe 3 places: primary button, active nav item, brand mark. Don't paint everything.\n\n### Off-Limits Customization (Breaks BFS)\n\n- Replacing Polaris typography with a custom font family\n- Changing border radius globally (Polaris has a tight radius system — don't redefine `--p-border-radius-*`)\n- Custom Button components that don't match Polaris button anatomy\n- Custom Card components with gradients, shadows, or borders not in the Polaris palette\n- Dark mode that Polaris hasn't sanctioned\n- Custom modal/dialog primitives (use `<ui-modal>` from App Bridge or Polaris `Modal`)\n- Replacing IndexTable with a custom table component\n\n### The Rule of Thumb\n\n**Override tokens, not components.** Polaris is fine with you re-coloring a button. Polaris is not fine with you replacing the button. If you find yourself building a \"FancyButton\" wrapper, you're crossing the line. If you find yourself adding a CSS variable override at the root, you're inside the lines.\n\n---\n\n## Micro-Interactions Polaris Allows\n\nModern apps feel alive because of small motion details — a hover lift, a focus ring, a check animation. Polaris ships some of these and tolerates others.\n\n### Built Into Polaris\n\n- Button hover (subtle bg color shift, ~150ms)\n- Focus ring on Tab navigation (a11y mandatory — never disable)\n- Modal fade-in / scale-in\n- Toast slide-up\n- Loading spinner on Button (when `loading` prop is true)\n\n### Acceptable Custom Micro-Interactions\n\n```css\n/* Subtle hover lift on cards in a grid */\n.dashboard-card {\n  transition: transform 150ms ease, box-shadow 150ms ease;\n}\n.dashboard-card:hover {\n  transform: translateY(-1px);\n  box-shadow: var(--p-shadow-200);\n}\n\n/* Number count-up animation for stats (use Framer Motion or react-spring) */\n/* OK if the duration is < 600ms and respects prefers-reduced-motion */\n\n/* Optimistic checkmark fade-in on save success */\n.save-checkmark {\n  animation: fadeIn 200ms ease;\n}\n@keyframes fadeIn {\n  from { opacity: 0; transform: scale(0.9); }\n  to { opacity: 1; transform: scale(1); }\n}\n```\n\n### Always Honor `prefers-reduced-motion`\n\n```css\n@media (prefers-reduced-motion: reduce) {\n  *, *::before, *::after {\n    animation-duration: 0.01ms !important;\n    transition-duration: 0.01ms !important;\n  }\n}\n```\n\n### Anti-Pattern: Animation Overload\n\nDon't animate everything. If three elements animate at once on page load, the app feels noisy, not modern. Linear has near-zero motion. Notion has near-zero motion. Vercel has near-zero motion. Restraint signals taste.\n\n---\n\n## Opinionated Defaults > Infinite Settings\n\nThe settings page is where SaaS apps go to die. Every config option is a decision the merchant didn't ask to make. The Linear/Notion answer: ship with the best default, surface a setting only if you can prove it matters.\n\n### Heuristic: Earn the Setting\n\nBefore adding a setting, ask:\n1. **Does this setting cause a 1-star review when it's wrong?** If no, don't add it.\n2. **Will more than 20% of merchants change the default?** If no, don't add it — ship the default and let the rare exceptions email support.\n3. **Can you auto-detect the right value?** Auto-detect beats asking. (Brand color from theme, currency from shop, timezone from shop.)\n4. **Can you defer the question to when it actually matters?** Don't ask at install — ask at first use.\n5. **Could two settings be one?** \"Email frequency: daily/weekly\" + \"Email enabled: yes/no\" should collapse to \"Email frequency: off/daily/weekly.\"\n\n### Sensible Default Examples\n\n| Setting | Bad default | Good default |\n|---|---|---|\n| Email sender | empty, requires merchant input | shop owner's name + shop name |\n| Brand color | #000000 | auto-detected from theme primary color |\n| Timezone | UTC | shop's configured timezone |\n| Currency | USD | shop's configured currency |\n| Notification frequency | \"Please choose\" | \"Daily digest\" |\n| First widget placement | \"Choose where\" | auto-placed via theme app extension |\n\n### The Settings Page Anatomy\n\nIf you must have a settings page:\n- Group settings into 3–5 sections max\n- Each section ≤ 5 settings\n- The most-changed setting at the top of each section\n- A \"Reset to defaults\" button (Polaris `Button variant=\"tertiary\"`)\n- A search input if you have > 15 total settings (and if you have > 15, you have too many)\n\n---\n\n## 15 Modern UX Patterns to Copy\n\nEach pattern lists the source app, what it does, and the Polaris/App Bridge implementation.\n\n### 1. Command Palette (Cmd+K)\n**Source:** Linear, Vercel, Raycast, Superhuman, Notion\n**Pattern:** Cmd+K opens a fuzzy-searchable list of every command and navigation target in the app.\n**Polaris implementation:** Polaris `Modal` + `Combobox` for a quick version, or `cmdk` library styled with Polaris tokens for the Linear-tier version. See \"Command Palette Inside Polaris\" above.\n\n### 2. Optimistic UI on Toggles\n**Source:** Linear (issue status), Vercel (deploy toggles), Notion (checkboxes)\n**Pattern:** Click flips state instantly, network request runs in background, revert on failure.\n**Polaris implementation:** `useFetcher` from Remix + local state. See \"Optimistic UI in Remix\" above.\n\n### 3. Prefetch on Hover\n**Source:** Vercel, Linear, Arc\n**Pattern:** Hover a link, the destination preloads, click feels teleportational.\n**Polaris implementation:** `<Link prefetch=\"intent\">` from Remix. Apply to every nav item.\n\n### 4. Slash Menu for Inline Actions\n**Source:** Notion, Linear (in comments)\n**Pattern:** Type `/` in any text field to insert blocks, mentions, or quick actions.\n**Polaris implementation:** Polaris `TextField` with a custom listener for `/`, opening a `Popover` with `Listbox` of actions.\n\n### 5. Inline Edit in Tables\n**Source:** Airtable, Notion, Linear\n**Pattern:** Click a table cell to edit it in place — no modal, no detail page.\n**Polaris implementation:** `IndexTable.Cell` with a `TextField` that activates on click, blur saves, Esc cancels.\n\n### 6. Breadcrumb-Driven Hierarchy\n**Source:** Notion, Linear (project > issue path)\n**Pattern:** Breadcrumb shows nested path, each segment is clickable, last segment is current page.\n**Polaris implementation:** Polaris `Page` accepts `backAction` for one-level back; for multi-level, use App Bridge `<ui-title-bar>` with custom breadcrumb in the title slot.\n\n### 7. Quiet Empty States\n**Source:** Linear, Cron\n**Pattern:** Empty state is a single sentence + one CTA. No illustrations, no marketing copy.\n**Polaris implementation:** Polaris `EmptyState` with `image` prop set to a minimal SVG (or none), `heading` one short sentence, `action` one button.\n\n### 8. Persistent Sidebar Search\n**Source:** Notion (top of sidebar), Linear (Cmd+/)\n**Pattern:** A search input persistently visible in the nav — not buried behind an icon.\n**Polaris implementation:** App Bridge `<ui-nav-menu>` doesn't support inline search natively. Add it just below the nav using Polaris `TextField` with `prefix={<Icon source={SearchIcon} />}`.\n\n### 9. Right-Side Detail Panel\n**Source:** Linear (click an issue, panel slides in from right), Notion (page peek)\n**Pattern:** Click a row, a panel slides in from the right with full detail. No navigation away.\n**Polaris implementation:** App Bridge does not ship a slide-over. Build with Polaris `Modal` set to `large` size and right-aligned via custom CSS, OR build a custom drawer with proper focus trap and `aria-modal`.\n\n### 10. Status Badges With Meaning\n**Source:** Linear (priority colors), Vercel (deploy status)\n**Pattern:** Tiny colored dot + label. Consistent color = consistent meaning across the app.\n**Polaris implementation:** Polaris `Badge` with `tone` prop (`success`, `warning`, `critical`, `info`, `attention`). Use the same tone for the same meaning everywhere.\n\n### 11. Keyboard Sequence Shortcuts\n**Source:** Linear (`G` then `D` = go to dashboard, Vim-style)\n**Pattern:** Two-key sequence shortcuts free up single keys. `G` opens a \"go to\" prompt; the next key picks the destination.\n**Polaris implementation:** `react-hotkeys-hook` supports sequences: `useHotkeys('g>d', ...)`.\n\n### 12. Live-Updating Numbers\n**Source:** Linear (issue counts), Vercel (deploy logs)\n**Pattern:** Numbers in the UI update in real time as data changes — no refresh button.\n**Polaris implementation:** Remix `useRevalidator` + polling, or `useFetcher` with interval, or websockets. Polaris doesn't fight you; the data layer does the work.\n\n### 13. Single Accent Color\n**Source:** Vercel (black + one purple), Linear (subtle indigo)\n**Pattern:** One accent, used in ≤ 3 places. Everything else is grayscale.\n**Polaris implementation:** Override `--p-color-bg-fill-brand` to your accent. Don't recolor anything else.\n\n### 14. Confirm-by-Type for Destructive Actions\n**Source:** Vercel, GitHub\n**Pattern:** Deleting a project? Type the project name to confirm. No accidental destruction.\n**Polaris implementation:** Polaris `Modal` with `TextField` inside, primaryAction `disabled` until the typed value matches.\n\n### 15. Toast Stack With Undo\n**Source:** Linear (every action has undo), Notion\n**Pattern:** Action succeeds, toast appears with \"Undo\" button, undo reverses the change for 5 seconds.\n**Polaris implementation:** Use App Bridge `shopify.toast()` with a custom action button, OR Polaris `Toast` with an `action` prop pointing to your undo handler.\n\n---\n\n## Anti-Patterns (Things That Break BFS or the Modern Feel)\n\n### 1. Custom Design System on Top of Polaris\nYou build `MyButton`, `MyCard`, `MyModal`, all wrapping Polaris with \"improvements.\" Six months in, you've drifted from Polaris and the BFS reviewer flags inconsistency with Shopify Admin. **Don't.** Override tokens, not components.\n\n### 2. Replacing Polaris Typography\nCustom fonts feel like \"branding\" but break the calm visual hierarchy Polaris enforces. Polaris uses Shopify's `Inter`-based stack; matching merchant typography expectations across the Admin is part of the BFS criteria.\n\n### 3. Animation Overload\nHero on page load, cards fade in staggered, sidebar slides, button pulses on hover, success checkmark spins. By the time the merchant has seen one screen they're motion-sick. **Pick one or two micro-interactions per surface, max.**\n\n### 4. Dark Mode Without Polaris's Blessing\nPolaris dark mode is still partial as of v12. Custom dark mode breaks token contracts. Wait for Polaris to ship full dark mode support.\n\n### 5. Custom Modal/Dialog Primitives\nBuilding a slide-over drawer with your own focus trap, escape handler, and backdrop is harder than it looks. Use App Bridge `<ui-modal>` or Polaris `Modal`. The custom version always misses an a11y case.\n\n### 6. Hiding Pricing Tier Behind a Custom UI\nPolaris has `Banner` for upgrade prompts; App Bridge has `<ui-modal>`. Don't build a custom paywall component — it'll feel un-Shopify.\n\n### 7. Infinite Settings\n40 settings on a page means you didn't make decisions. The merchant pays for your indecision in cognitive load. Cut.\n\n### 8. Confirmation Modals for Reversible Actions\nLinear never asks \"Are you sure?\" for things you can undo. Modern apps trust users and provide undo. Confirm only for truly destructive actions.\n\n### 9. Loading Spinners Under 300ms\nAdding a spinner for a 150ms request makes the request feel slower, not faster. Use optimistic UI or no indicator at all.\n\n### 10. Breaking Cmd+S / Esc / Tab Order\nApp Bridge owns Cmd+S. Polaris Modal owns Esc. Tab order is governed by DOM order. Don't fight these.\n\n---\n\n## Decision Tree\n\nUse this when deciding whether to add a \"modern feel\" feature to your app.\n\n```\nQuestion: Should I add this UX pattern to my Shopify app?\n\n├─ Does Polaris ship a primitive for it?\n│  ├─ Yes → Use the Polaris primitive. Stop. ✓\n│  └─ No → continue\n│\n├─ Is the pattern a keyboard shortcut?\n│  ├─ Yes → Add via react-hotkeys-hook. Ensure visible alternative.\n│  │       Add to the \"?\" help dialog. Stop. ✓\n│  └─ No → continue\n│\n├─ Is the pattern a command palette?\n│  ├─ Yes → Start with Polaris Modal + Combobox.\n│  │       Upgrade to cmdk only if you need fuzzy/grouped/recents. Stop. ✓\n│  └─ No → continue\n│\n├─ Is the pattern a micro-interaction (hover, focus, transition)?\n│  ├─ Yes → ≤ 200ms duration? Respects prefers-reduced-motion?\n│  │       ├─ Yes → Ship it. Stop. ✓\n│  │       └─ No → Don't ship.\n│  └─ No → continue\n│\n├─ Is the pattern a brand color / accent override?\n│  ├─ Yes → Override the relevant --p-color-* token at the root.\n│  │       Don't override component-level styles. Stop. ✓\n│  └─ No → continue\n│\n├─ Is the pattern a custom layout / typography / component?\n│  ├─ Yes → STOP. You're outside Polaris. BFS risk.\n│  │       Re-frame the problem: can you achieve this with Polaris primitives?\n│  │       If truly impossible, document why and proceed cautiously.\n│  └─ No → continue\n│\n└─ Is the pattern an opinionated default replacing a setting?\n   ├─ Yes → Ship it. The merchant gets less choice, more speed. ✓\n   └─ No → re-read this skill. The answer is probably \"ship a default.\"\n```\n\n---\n\n## Checklist: Modern Shopify App Feel\n\nBefore shipping, verify:\n\n- [ ] App Bridge 4.x web components used for save bar, modal, toast (not custom)\n- [ ] Polaris 12.x for all UI primitives (Button, Card, TextField, IndexTable, etc.)\n- [ ] Cmd+K opens a command palette\n- [ ] `/` focuses the primary search input on every list page\n- [ ] `C` creates a new entity on every list page\n- [ ] `?` opens a keyboard shortcuts help dialog\n- [ ] Every primary action button shows its shortcut next to the label\n- [ ] Every nav `<Link>` has `prefetch=\"intent\"`\n- [ ] Optimistic UI on every toggle / status change\n- [ ] No loading spinners shown for requests under 300ms\n- [ ] Skeleton screens match real layout dimensions (no layout shift)\n- [ ] Power-user surfaces use IndexTable with `condensed` + `padding=\"200\"`\n- [ ] Settings surfaces use spacious defaults (gap 400, bodyMd)\n- [ ] Brand accent overrides `--p-color-bg-fill-brand` only\n- [ ] No custom font families (use Polaris/Shopify default)\n- [ ] All micro-interactions ≤ 200ms and honor `prefers-reduced-motion`\n- [ ] Every destructive action is undoable OR uses confirm-by-type\n- [ ] Toast with \"Undo\" appears after every reversible action\n- [ ] Settings page has ≤ 3 sections, ≤ 5 settings per section\n- [ ] First-run experience has no questionnaire — app works immediately\n- [ ] Empty states are one sentence + one CTA, no illustrations beyond a single tasteful SVG\n- [ ] One accent color used in ≤ 3 places across the app\n\nIf every box is checked, your Shopify app feels like Linear inside Polaris. Which is the goal.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}