shopify-polaris-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
This skill ensures that interfaces are built using Shopify's Polaris Design System (React implementation v13.x), guaranteeing a native, accessible, and professional look and feel for Shopify Merchants.
Note (2025-2026): Polaris React (
@shopify/polaris
) is in maintenance mode. Shopify has introduced Polaris Web Components for new development. However, Polaris React remains fully functional and supported for existing applications. This guide covers the React implementation.
本技能可确保界面基于Shopify的Polaris Design System(React实现版本v13.x)构建,为Shopify商家提供原生、无障碍、专业的外观和体验。
注意(2025-2026):Polaris React(
@shopify/polaris
)目前处于维护模式。Shopify已推出Polaris Web Components用于新开发项目,但Polaris React对现有应用仍完全可用并提供支持。本指南覆盖React实现版本的相关内容。

Core Principles

核心原则

  1. Merchant-Focused: Design for efficiency and clarity. Merchants use these tools to run their business.
  2. Native Feel: The app should feel like a natural extension of the Shopify Admin. Do not introduce foreign design patterns (e.g. Material Design shadows, distinct bootstappy buttons) unless absolutely necessary.
  3. Accessibility: Polaris is built with accessibility in mind. Maintain this by using semantic components (e.g.,
    Button
    ,
    Link
    ,
    TextField
    ) rather than custom
    div
    implementations.
  4. Predictability: Follow standard Shopify patterns. Save buttons go in the App Bridge Save Bar. Page actions go in the top right. Primary content is centered.
  1. 以商家为中心:设计优先考虑效率和清晰度,商家使用这些工具运营他们的业务。
  2. 原生体验:应用应该像是Shopify Admin的自然延伸,除非绝对必要,不要引入外来设计模式(例如Material Design阴影、风格突出的bootstrap类按钮)。
  3. 无障碍:Polaris原生支持无障碍特性,你可以通过使用语义化组件(例如
    Button
    Link
    TextField
    )而非自定义
    div
    实现来保留该特性。
  4. 可预测性:遵循标准的Shopify设计模式:保存按钮放在App Bridge保存栏中,页面操作放在右上角,主要内容居中展示。

Technical Implementation

技术实现

Dependencies (v13.x - 2025-2026)

依赖(v13.x - 2025-2026)

json
{
  "@shopify/polaris": "^13.9.0",
  "@shopify/polaris-icons": "^9.x",
  "@shopify/app-bridge-react": "^4.x"
}
json
{
  "@shopify/polaris": "^13.9.0",
  "@shopify/polaris-icons": "^9.x",
  "@shopify/app-bridge-react": "^4.x"
}

App Bridge Integration (Critical for v13.x)

App Bridge集成(v13.x版本核心要求)

Many UI components are now handled by App Bridge instead of Polaris React:
Deprecated Polaris ComponentUse App Bridge Instead
Modal
@shopify/app-bridge-react
Modal API
Navigation
App Bridge Navigation Menu API
Toast
App Bridge Toast API
ContextualSaveBar
App Bridge
useSaveBar()
hook
TopBar
App Bridge Title Bar API
Loading
App Bridge Loading API
jsx
// Example: Using App Bridge for Modal (instead of deprecated Polaris Modal)
import { Modal, TitleBar } from '@shopify/app-bridge-react';

function MyComponent() {
  return (
    <Modal id="my-modal">
      <TitleBar title="Confirm Action">
        <button variant="primary" onClick={handleConfirm}>Confirm</button>
        <button onClick={handleCancel}>Cancel</button>
      </TitleBar>
      <p>Are you sure you want to proceed?</p>
    </Modal>
  );
}

// Example: Using App Bridge for Toast
import { useAppBridge } from '@shopify/app-bridge-react';

function showToast() {
  shopify.toast.show('Product saved successfully');
}

// Example: Using App Bridge Save Bar
import { useSaveBar } from '@shopify/app-bridge-react';

function SettingsForm() {
  const saveBar = useSaveBar();

  useEffect(() => {
    if (hasChanges) {
      saveBar.show();
    } else {
      saveBar.hide();
    }
  }, [hasChanges]);
}
现在很多UI组件由App Bridge而非Polaris React提供:
已废弃的Polaris组件替换为对应的App Bridge能力
Modal
@shopify/app-bridge-react
Modal API
Navigation
App Bridge Navigation Menu API
Toast
App Bridge Toast API
ContextualSaveBar
App Bridge
useSaveBar()
hook
TopBar
App Bridge Title Bar API
Loading
App Bridge Loading API
jsx
// 示例:使用App Bridge实现Modal(替代已废弃的Polaris Modal)
import { Modal, TitleBar } from '@shopify/app-bridge-react';

function MyComponent() {
  return (
    <Modal id="my-modal">
      <TitleBar title="Confirm Action">
        <button variant="primary" onClick={handleConfirm}>Confirm</button>
        <button onClick={handleCancel}>Cancel</button>
      </TitleBar>
      <p>Are you sure you want to proceed?</p>
    </Modal>
  );
}

// 示例:使用App Bridge实现Toast
import { useAppBridge } from '@shopify/app-bridge-react';

function showToast() {
  shopify.toast.show('Product saved successfully');
}

// 示例:使用App Bridge保存栏
import { useSaveBar } from '@shopify/app-bridge-react';

function SettingsForm() {
  const saveBar = useSaveBar();

  useEffect(() => {
    if (hasChanges) {
      saveBar.show();
    } else {
      saveBar.hide();
    }
  }, [hasChanges]);
}

Fundamental Components

基础组件

  • AppProvider: All Polaris apps must be wrapped in
    <AppProvider i18n={enTranslations}>
    .
  • Page: The top-level container for a route. Always set
    title
    and
    primaryAction
    (if applicable).
    jsx
    <Page
      title="Products"
      primaryAction={{content: 'Add product', onAction: handleAdd}}
      backAction={{content: 'Settings', url: '/settings'}}
    >
    v13.x Note:
    backAction
    prop in
    Page.Header
    is deprecated. Use App Bridge navigation instead for complex navigation patterns.
  • Layout: Use
    Layout
    and
    Layout.Section
    to structure content.
    • Layout.AnnotatedSection
      : For settings pages (Title/Description on left, Card on right).
    • Layout.Section
      : Standard Full (default), 1/2 (
      variant="oneHalf"
      ), or 1/3 (
      variant="oneThird"
      ) width columns.
  • Card: The primary container for content pieces. Group related information in a Card.
    • Use
      BlockStack
      (vertical) or
      InlineStack
      (horizontal) for internal layout within a Card.
    • Do not use
      LegacyCard
      - it is deprecated. Use
      Card
      with layout primitives.
  • AppProvider:所有Polaris应用必须包裹在
    <AppProvider i18n={enTranslations}>
    中。
  • Page:路由的顶层容器,始终设置
    title
    primaryAction
    (如果适用)。
    jsx
    <Page
      title="Products"
      primaryAction={{content: 'Add product', onAction: handleAdd}}
      backAction={{content: 'Settings', url: '/settings'}}
    >
    v13.x注意事项
    Page.Header
    中的
    backAction
    属性已废弃,复杂导航场景请使用App Bridge导航能力替代。
  • Layout:使用
    Layout
    Layout.Section
    来组织内容结构。
    • Layout.AnnotatedSection
      :用于设置页面(左侧为标题/描述,右侧为卡片)。
    • Layout.Section
      :标准全宽(默认)、1/2宽(
      variant="oneHalf"
      )或1/3宽(
      variant="oneThird"
      )列。
  • Card:内容块的主要容器,将相关信息分组放在Card中。
    • 在Card内部使用
      BlockStack
      (垂直布局)或
      InlineStack
      (水平布局)做内部布局。
    • 不要使用
      LegacyCard
      ,它已被废弃,请使用
      Card
      搭配布局原语。

Layout Primitives (Modern Pattern)

布局原语(现代模式)

jsx
import { Box, BlockStack, InlineStack, InlineGrid, Bleed, Divider } from '@shopify/polaris';

// Box - Low-level layout primitive with full token access
<Box padding="400" background="bg-surface-secondary" borderRadius="200">
  Content here
</Box>

// BlockStack - Vertical stacking with gap
<BlockStack gap="400">
  <Item1 />
  <Item2 />
</BlockStack>

// InlineStack - Horizontal layout with alignment
<InlineStack gap="200" align="center" blockAlign="center">
  <Icon />
  <Text>Label</Text>
</InlineStack>

// InlineGrid - Responsive grid layout
<InlineGrid columns={{xs: 1, sm: 2, md: 3}} gap="400">
  <Card>...</Card>
  <Card>...</Card>
  <Card>...</Card>
</InlineGrid>

// Bleed - Negative margin for edge-to-edge content
<Card>
  <Bleed marginInline="400">
    <img src="banner.jpg" style={{width: '100%'}} />
  </Bleed>
</Card>
jsx
import { Box, BlockStack, InlineStack, InlineGrid, Bleed, Divider } from '@shopify/polaris';

// Box - 支持完整令牌访问的底层布局原语
<Box padding="400" background="bg-surface-secondary" borderRadius="200">
  Content here
</Box>

// BlockStack - 带间距的垂直堆叠布局
<BlockStack gap="400">
  <Item1 />
  <Item2 />
</BlockStack>

// InlineStack - 带对齐配置的水平布局
<InlineStack gap="200" align="center" blockAlign="center">
  <Icon />
  <Text>Label</Text>
</InlineStack>

// InlineGrid - 响应式网格布局
<InlineGrid columns={{xs: 1, sm: 2, md: 3}} gap="400">
  <Card>...</Card>
  <Card>...</Card>
  <Card>...</Card>
</InlineGrid>

// Bleed - 负边距实现边到边的内容展示
<Card>
  <Bleed marginInline="400">
    <img src="banner.jpg" style={{width: '100%'}} />
  </Bleed>
</Card>

Data Display

数据展示

  • IndexTable: For lists of objects (Products, Orders) with bulk actions and filtering.
    jsx
    import { IndexTable, Card, Text, Badge, useIndexResourceState } from '@shopify/polaris';
    
    function ProductList({ products }) {
      const { selectedResources, allResourcesSelected, handleSelectionChange } =
        useIndexResourceState(products);
    
      const rowMarkup = products.map((product, index) => (
        <IndexTable.Row
          id={product.id}
          key={product.id}
          selected={selectedResources.includes(product.id)}
          position={index}
        >
          <IndexTable.Cell>
            <Text variant="bodyMd" fontWeight="bold">{product.name}</Text>
          </IndexTable.Cell>
          <IndexTable.Cell>{product.sku}</IndexTable.Cell>
          <IndexTable.Cell>
            <Badge tone={product.status === 'active' ? 'success' : 'info'}>
              {product.status}
            </Badge>
          </IndexTable.Cell>
        </IndexTable.Row>
      ));
    
      return (
        <Card padding="0">
          <IndexTable
            resourceName={{singular: 'product', plural: 'products'}}
            itemCount={products.length}
            selectedItemsCount={allResourcesSelected ? 'All' : selectedResources.length}
            onSelectionChange={handleSelectionChange}
            headings={[
              {title: 'Name'},
              {title: 'SKU'},
              {title: 'Status'},
            ]}
          >
            {rowMarkup}
          </IndexTable>
        </Card>
      );
    }
  • DataTable: For simple, non-interactive data grids (e.g., analytics data).
  • ResourceList: For simpler lists without table structure (use
    ResourceItem
    for each item).
  • IndexTable:用于展示对象列表(商品、订单),支持批量操作和筛选。
    jsx
    import { IndexTable, Card, Text, Badge, useIndexResourceState } from '@shopify/polaris';
    
    function ProductList({ products }) {
      const { selectedResources, allResourcesSelected, handleSelectionChange } =
        useIndexResourceState(products);
    
      const rowMarkup = products.map((product, index) => (
        <IndexTable.Row
          id={product.id}
          key={product.id}
          selected={selectedResources.includes(product.id)}
          position={index}
        >
          <IndexTable.Cell>
            <Text variant="bodyMd" fontWeight="bold">{product.name}</Text>
          </IndexTable.Cell>
          <IndexTable.Cell>{product.sku}</IndexTable.Cell>
          <IndexTable.Cell>
            <Badge tone={product.status === 'active' ? 'success' : 'info'}>
              {product.status}
            </Badge>
          </IndexTable.Cell>
        </IndexTable.Row>
      ));
    
      return (
        <Card padding="0">
          <IndexTable
            resourceName={{singular: 'product', plural: 'products'}}
            itemCount={products.length}
            selectedItemsCount={allResourcesSelected ? 'All' : selectedResources.length}
            onSelectionChange={handleSelectionChange}
            headings={[
              {title: 'Name'},
              {title: 'SKU'},
              {title: 'Status'},
            ]}
          >
            {rowMarkup}
          </IndexTable>
        </Card>
      );
    }
  • DataTable:用于简单的非交互式数据网格(例如分析数据)。
  • ResourceList:用于没有表格结构的简单列表(每个条目使用
    ResourceItem
    实现)。

Form Design

表单设计

jsx
import {
  Form, FormLayout, TextField, Select, Checkbox,
  ChoiceList, RadioButton, RangeSlider, ColorPicker,
  DropZone, Tag, Autocomplete
} from '@shopify/polaris';

function ProductForm() {
  const [formState, setFormState] = useState({
    title: '',
    description: '',
    status: 'draft',
    tags: [],
  });
  const [errors, setErrors] = useState({});

  return (
    <Form onSubmit={handleSubmit}>
      <FormLayout>
        <TextField
          label="Product title"
          value={formState.title}
          onChange={(value) => setFormState({...formState, title: value})}
          error={errors.title}
          autoComplete="off"
          helpText="This will be displayed to customers"
        />

        <TextField
          label="Description"
          value={formState.description}
          onChange={(value) => setFormState({...formState, description: value})}
          multiline={4}
          autoComplete="off"
        />

        <Select
          label="Status"
          options={[
            {label: 'Draft', value: 'draft'},
            {label: 'Active', value: 'active'},
            {label: 'Archived', value: 'archived'},
          ]}
          value={formState.status}
          onChange={(value) => setFormState({...formState, status: value})}
        />

        <FormLayout.Group>
          <TextField label="Price" type="number" prefix="$" />
          <TextField label="Compare at price" type="number" prefix="$" />
        </FormLayout.Group>

        <ChoiceList
          title="Availability"
          choices={[
            {label: 'Online Store', value: 'online'},
            {label: 'Point of Sale', value: 'pos'},
            {label: 'Buy Button', value: 'buy_button'},
          ]}
          selected={formState.channels}
          onChange={(value) => setFormState({...formState, channels: value})}
          allowMultiple
        />
      </FormLayout>
    </Form>
  );
}
jsx
import {
  Form, FormLayout, TextField, Select, Checkbox,
  ChoiceList, RadioButton, RangeSlider, ColorPicker,
  DropZone, Tag, Autocomplete
} from '@shopify/polaris';

function ProductForm() {
  const [formState, setFormState] = useState({
    title: '',
    description: '',
    status: 'draft',
    tags: [],
  });
  const [errors, setErrors] = useState({});

  return (
    <Form onSubmit={handleSubmit}>
      <FormLayout>
        <TextField
          label="Product title"
          value={formState.title}
          onChange={(value) => setFormState({...formState, title: value})}
          error={errors.title}
          autoComplete="off"
          helpText="This will be displayed to customers"
        />

        <TextField
          label="Description"
          value={formState.description}
          onChange={(value) => setFormState({...formState, description: value})}
          multiline={4}
          autoComplete="off"
        />

        <Select
          label="Status"
          options={[
            {label: 'Draft', value: 'draft'},
            {label: 'Active', value: 'active'},
            {label: 'Archived', value: 'archived'},
          ]}
          value={formState.status}
          onChange={(value) => setFormState({...formState, status: value})}
        />

        <FormLayout.Group>
          <TextField label="Price" type="number" prefix="$" />
          <TextField label="Compare at price" type="number" prefix="$" />
        </FormLayout.Group>

        <ChoiceList
          title="Availability"
          choices={[
            {label: 'Online Store', value: 'online'},
            {label: 'Point of Sale', value: 'pos'},
            {label: 'Buy Button', value: 'buy_button'},
          ]}
          selected={formState.channels}
          onChange={(value) => setFormState({...formState, channels: value})}
          allowMultiple
        />
      </FormLayout>
    </Form>
  );
}

Filters & Search (IndexFilters)

筛选与搜索(IndexFilters)

jsx
import {
  IndexFilters, useSetIndexFiltersMode, IndexFiltersMode,
  ChoiceList, RangeSlider, TextField
} from '@shopify/polaris';

function FilteredList() {
  const [queryValue, setQueryValue] = useState('');
  const [status, setStatus] = useState([]);
  const { mode, setMode } = useSetIndexFiltersMode(IndexFiltersMode.Filtering);

  const filters = [
    {
      key: 'status',
      label: 'Status',
      filter: (
        <ChoiceList
          title="Status"
          titleHidden
          choices={[
            {label: 'Active', value: 'active'},
            {label: 'Draft', value: 'draft'},
            {label: 'Archived', value: 'archived'},
          ]}
          selected={status}
          onChange={setStatus}
          allowMultiple
        />
      ),
      shortcut: true,
    },
  ];

  const appliedFilters = status.length > 0
    ? [{key: 'status', label: `Status: ${status.join(', ')}`}]
    : [];

  return (
    <IndexFilters
      queryValue={queryValue}
      queryPlaceholder="Search products"
      onQueryChange={setQueryValue}
      onQueryClear={() => setQueryValue('')}
      filters={filters}
      appliedFilters={appliedFilters}
      onClearAll={() => setStatus([])}
      mode={mode}
      setMode={setMode}
      tabs={[
        {content: 'All', id: 'all'},
        {content: 'Active', id: 'active'},
        {content: 'Draft', id: 'draft'},
      ]}
      selected={0}
    />
  );
}
jsx
import {
  IndexFilters, useSetIndexFiltersMode, IndexFiltersMode,
  ChoiceList, RangeSlider, TextField
} from '@shopify/polaris';

function FilteredList() {
  const [queryValue, setQueryValue] = useState('');
  const [status, setStatus] = useState([]);
  const { mode, setMode } = useSetIndexFiltersMode(IndexFiltersMode.Filtering);

  const filters = [
    {
      key: 'status',
      label: 'Status',
      filter: (
        <ChoiceList
          title="Status"
          titleHidden
          choices={[
            {label: 'Active', value: 'active'},
            {label: 'Draft', value: 'draft'},
            {label: 'Archived', value: 'archived'},
          ]}
          selected={status}
          onChange={setStatus}
          allowMultiple
        />
      ),
      shortcut: true,
    },
  ];

  const appliedFilters = status.length > 0
    ? [{key: 'status', label: `Status: ${status.join(', ')}`}]
    : [];

  return (
    <IndexFilters
      queryValue={queryValue}
      queryPlaceholder="Search products"
      onQueryChange={setQueryValue}
      onQueryClear={() => setQueryValue('')}
      filters={filters}
      appliedFilters={appliedFilters}
      onClearAll={() => setStatus([])}
      mode={mode}
      setMode={setMode}
      tabs={[
        {content: 'All', id: 'all'},
        {content: 'Active', id: 'active'},
        {content: 'Draft', id: 'draft'},
      ]}
      selected={0}
    />
  );
}

Design Tokens & CSS (v13.x)

设计令牌与CSS(v13.x)

  • Avoid Custom CSS: 95% of styling should be handled by Polaris props (
    gap
    ,
    padding
    ,
    align
    ,
    justify
    ).
  • Design Tokens: If you MUST use custom CSS, use Polaris CSS Custom Properties (Tokens).
  • 避免自定义CSS:95%的样式需求可以通过Polaris属性(
    gap
    padding
    align
    justify
    )实现。
  • 设计令牌:如果你必须使用自定义CSS,请使用Polaris CSS自定义属性(令牌)。

Spacing Tokens (4px base)

间距令牌(基础单位4px)

TokenValueUsage
--p-space-050
2pxMinimal spacing
--p-space-100
4pxTight spacing
--p-space-200
8pxCompact spacing
--p-space-300
12pxDefault small
--p-space-400
16pxDefault standard
--p-space-500
20pxMedium spacing
--p-space-600
24pxLarge spacing
--p-space-800
32pxSection spacing
--p-space-1000
40pxPage spacing
--p-space-1200
48pxExtra large
令牌取值使用场景
--p-space-050
2px极小间距
--p-space-100
4px紧凑间距
--p-space-200
8px紧凑型间距
--p-space-300
12px默认小间距
--p-space-400
16px默认标准间距
--p-space-500
20px中等间距
--p-space-600
24px大间距
--p-space-800
32px区块间距
--p-space-1000
40px页面间距
--p-space-1200
48px超大间距

Color Tokens

颜色令牌

css
/* Backgrounds */
--p-color-bg                     /* Default page background */
--p-color-bg-surface             /* Card/surface background */
--p-color-bg-surface-secondary   /* Secondary surface */
--p-color-bg-surface-hover       /* Hover state */
--p-color-bg-surface-selected    /* Selected state */
--p-color-bg-fill-brand          /* Primary brand fill */
--p-color-bg-fill-success        /* Success background */
--p-color-bg-fill-warning        /* Warning background */
--p-color-bg-fill-critical       /* Critical/error background */

/* Text */
--p-color-text                   /* Default text */
--p-color-text-secondary         /* Subdued text */
--p-color-text-disabled          /* Disabled text */
--p-color-text-brand             /* Brand colored text */
--p-color-text-success           /* Success text */
--p-color-text-warning           /* Warning text */
--p-color-text-critical          /* Error text */

/* Borders */
--p-color-border                 /* Default border */
--p-color-border-hover           /* Hover border */
--p-color-border-focus           /* Focus ring */
--p-color-border-brand           /* Brand border */
css
/* 背景色 */
--p-color-bg                     /* 默认页面背景 */
--p-color-bg-surface             /* 卡片/容器背景 */
--p-color-bg-surface-secondary   /* 二级容器背景 */
--p-color-bg-surface-hover       /* 悬停状态背景 */
--p-color-bg-surface-selected    /* 选中状态背景 */
--p-color-bg-fill-brand          /* 品牌主色填充 */
--p-color-bg-fill-success        /* 成功状态背景 */
--p-color-bg-fill-warning        /* 警告状态背景 */
--p-color-bg-fill-critical       /* 严重/错误状态背景 */

/* 文本色 */
--p-color-text                   /* 默认文本色 */
--p-color-text-secondary         /* 弱化文本色 */
--p-color-text-disabled          /* 禁用状态文本色 */
--p-color-text-brand             /* 品牌色文本 */
--p-color-text-success           /* 成功状态文本 */
--p-color-text-warning           /* 警告状态文本 */
--p-color-text-critical          /* 错误状态文本 */

/* 边框色 */
--p-color-border                 /* 默认边框 */
--p-color-border-hover           /* 悬停状态边框 */
--p-color-border-focus           /* 聚焦环 */
--p-color-border-brand           /* 品牌色边框 */

Border Radius Tokens

圆角令牌

css
--p-border-radius-100    /* 4px - Small elements */
--p-border-radius-200    /* 8px - Cards, buttons */
--p-border-radius-300    /* 12px - Large cards */
--p-border-radius-full   /* 9999px - Pills, avatars */
css
--p-border-radius-100    /* 4px - 小型元素 */
--p-border-radius-200    /* 8px - 卡片、按钮 */
--p-border-radius-300    /* 12px - 大型卡片 */
--p-border-radius-full   /* 9999px - 胶囊形状、头像 */

Shadow Tokens

阴影令牌

css
--p-shadow-100   /* Subtle shadow */
--p-shadow-200   /* Card shadow */
--p-shadow-300   /* Elevated shadow */
--p-shadow-400   /* Modal shadow */
css
--p-shadow-100   /* 淡阴影 */
--p-shadow-200   /* 卡片阴影 */
--p-shadow-300   /* 抬高元素阴影 */
--p-shadow-400   /* 弹窗阴影 */

Typography

排版

jsx
// Use Text component with variants instead of HTML tags
<Text variant="headingXl">Page Title</Text>      // 28px bold
<Text variant="headingLg">Section Title</Text>   // 24px bold
<Text variant="headingMd">Card Title</Text>      // 20px semibold
<Text variant="headingSm">Subsection</Text>      // 16px semibold
<Text variant="headingXs">Small Header</Text>    // 14px semibold
<Text variant="bodyLg">Large body</Text>         // 16px regular
<Text variant="bodyMd">Default body</Text>       // 14px regular
<Text variant="bodySm">Small text</Text>         // 12px regular

// Tones for semantic meaning
<Text tone="subdued">Secondary information</Text>
<Text tone="success">Success message</Text>
<Text tone="critical">Error message</Text>
<Text tone="caution">Warning message</Text>
jsx
// 使用带variant属性的Text组件替代原生HTML标签
<Text variant="headingXl">Page Title</Text>      // 28px 粗体
<Text variant="headingLg">Section Title</Text>   // 24px 粗体
<Text variant="headingMd">Card Title</Text>      // 20px 半粗体
<Text variant="headingSm">Subsection</Text>      // 16px 半粗体
<Text variant="headingXs">Small Header</Text>    // 14px 半粗体
<Text variant="bodyLg">Large body</Text>         // 16px 常规字重
<Text variant="bodyMd">Default body</Text>       // 14px 常规字重
<Text variant="bodySm">Small text</Text>         // 12px 常规字重

// 语义化tone属性
<Text tone="subdued">Secondary information</Text>
<Text tone="success">Success message</Text>
<Text tone="critical">Error message</Text>
<Text tone="caution">Warning message</Text>

Deprecated Components (v13.x) - DO NOT USE

已废弃组件(v13.x)- 请勿使用

These components will be removed in future versions:
DeprecatedUse Instead
LegacyCard
Card
+
BlockStack
LegacyStack
BlockStack
/
InlineStack
LegacyFilters
IndexFilters
LegacyTabs
Tabs
Modal
App Bridge Modal API
Navigation
App Bridge Navigation Menu
Toast
App Bridge Toast API
ContextualSaveBar
App Bridge
useSaveBar()
TopBar
App Bridge Title Bar
Loading
App Bridge Loading API
Frame
App Bridge handles this
Sheet
App Bridge Modal or custom
DisplayText
Text
with
variant="heading*"
Heading
Text
with
variant="heading*"
Subheading
Text
with
variant="headingSm"
Caption
Text
with
variant="bodySm"
TextStyle
Text
with
tone
prop
TextContainer
BlockStack
with gap
SettingToggle
Custom with
Card
+
InlineStack
+
Button
PageActions
Page
primaryAction
/
secondaryActions
props
VisuallyHidden
Use
visuallyHidden
prop on Text
这些组件将在未来版本中被移除:
已废弃组件替代方案
LegacyCard
Card
+
BlockStack
LegacyStack
BlockStack
/
InlineStack
LegacyFilters
IndexFilters
LegacyTabs
Tabs
Modal
App Bridge Modal API
Navigation
App Bridge Navigation Menu
Toast
App Bridge Toast API
ContextualSaveBar
App Bridge
useSaveBar()
TopBar
App Bridge Title Bar
Loading
App Bridge Loading API
Frame
由App Bridge自动处理
Sheet
App Bridge Modal或自定义实现
DisplayText
Text
搭配
variant="heading*"
Heading
Text
搭配
variant="heading*"
Subheading
Text
搭配
variant="headingSm"
Caption
Text
搭配
variant="bodySm"
TextStyle
Text
搭配
tone
属性
TextContainer
带gap的
BlockStack
SettingToggle
自定义实现:
Card
+
InlineStack
+
Button
PageActions
Page
primaryAction
/
secondaryActions
属性
VisuallyHidden
使用Text组件的
visuallyHidden
属性

Code Style Example (v13.x Best Practices)

代码风格示例(v13.x最佳实践)

jsx
import {
  Page, Layout, Card, BlockStack, InlineStack,
  Text, Button, Badge, Box, Divider, Banner,
  IndexTable, useIndexResourceState
} from '@shopify/polaris';
import { ExportIcon, PlusIcon } from '@shopify/polaris-icons';

export default function Dashboard({ products }) {
  const { selectedResources, allResourcesSelected, handleSelectionChange } =
    useIndexResourceState(products);

  const promotedBulkActions = [
    { content: 'Export selected', icon: ExportIcon },
  ];

  const rowMarkup = products.map((product, index) => (
    <IndexTable.Row
      id={product.id}
      key={product.id}
      selected={selectedResources.includes(product.id)}
      position={index}
    >
      <IndexTable.Cell>
        <Text variant="bodyMd" fontWeight="bold">{product.title}</Text>
      </IndexTable.Cell>
      <IndexTable.Cell>
        <Badge tone={product.status === 'active' ? 'success' : 'info'}>
          {product.status}
        </Badge>
      </IndexTable.Cell>
      <IndexTable.Cell>${product.price}</IndexTable.Cell>
    </IndexTable.Row>
  ));

  return (
    <Page
      title="Dashboard"
      primaryAction={{
        content: 'Add product',
        icon: PlusIcon,
        onAction: () => {}
      }}
      secondaryActions={[
        {content: 'Export', icon: ExportIcon, onAction: () => {}}
      ]}
    >
      <Layout>
        <Layout.Section>
          <Banner tone="info" onDismiss={() => {}}>
            <p>New: Try our improved bulk editing features.</p>
          </Banner>
        </Layout.Section>

        <Layout.Section>
          <Card padding="0">
            <IndexTable
              resourceName={{singular: 'product', plural: 'products'}}
              itemCount={products.length}
              selectedItemsCount={allResourcesSelected ? 'All' : selectedResources.length}
              onSelectionChange={handleSelectionChange}
              headings={[
                {title: 'Product'},
                {title: 'Status'},
                {title: 'Price', alignment: 'end'},
              ]}
              promotedBulkActions={promotedBulkActions}
            >
              {rowMarkup}
            </IndexTable>
          </Card>
        </Layout.Section>

        <Layout.Section variant="oneThird">
          <Card>
            <BlockStack gap="400">
              <Text as="h2" variant="headingMd">Quick Stats</Text>
              <Divider />
              <BlockStack gap="200">
                <InlineStack align="space-between">
                  <Text tone="subdued">Total products</Text>
                  <Text fontWeight="semibold">{products.length}</Text>
                </InlineStack>
                <InlineStack align="space-between">
                  <Text tone="subdued">Active</Text>
                  <Text fontWeight="semibold">
                    {products.filter(p => p.status === 'active').length}
                  </Text>
                </InlineStack>
              </BlockStack>
            </BlockStack>
          </Card>

          <Box paddingBlockStart="400">
            <Card>
              <BlockStack gap="300">
                <Text as="h3" variant="headingSm">Quick Actions</Text>
                <Button variant="plain" url="/settings">
                  View all settings
                </Button>
              </BlockStack>
            </Card>
          </Box>
        </Layout.Section>
      </Layout>
    </Page>
  );
}
jsx
import {
  Page, Layout, Card, BlockStack, InlineStack,
  Text, Button, Badge, Box, Divider, Banner,
  IndexTable, useIndexResourceState
} from '@shopify/polaris';
import { ExportIcon, PlusIcon } from '@shopify/polaris-icons';

export default function Dashboard({ products }) {
  const { selectedResources, allResourcesSelected, handleSelectionChange } =
    useIndexResourceState(products);

  const promotedBulkActions = [
    { content: 'Export selected', icon: ExportIcon },
  ];

  const rowMarkup = products.map((product, index) => (
    <IndexTable.Row
      id={product.id}
      key={product.id}
      selected={selectedResources.includes(product.id)}
      position={index}
    >
      <IndexTable.Cell>
        <Text variant="bodyMd" fontWeight="bold">{product.title}</Text>
      </IndexTable.Cell>
      <IndexTable.Cell>
        <Badge tone={product.status === 'active' ? 'success' : 'info'}>
          {product.status}
        </Badge>
      </IndexTable.Cell>
      <IndexTable.Cell>${product.price}</IndexTable.Cell>
    </IndexTable.Row>
  ));

  return (
    <Page
      title="Dashboard"
      primaryAction={{
        content: 'Add product',
        icon: PlusIcon,
        onAction: () => {}
      }}
      secondaryActions={[
        {content: 'Export', icon: ExportIcon, onAction: () => {}}
      ]}
    >
      <Layout>
        <Layout.Section>
          <Banner tone="info" onDismiss={() => {}}>
            <p>New: Try our improved bulk editing features.</p>
          </Banner>
        </Layout.Section>

        <Layout.Section>
          <Card padding="0">
            <IndexTable
              resourceName={{singular: 'product', plural: 'products'}}
              itemCount={products.length}
              selectedItemsCount={allResourcesSelected ? 'All' : selectedResources.length}
              onSelectionChange={handleSelectionChange}
              headings={[
                {title: 'Product'},
                {title: 'Status'},
                {title: 'Price', alignment: 'end'},
              ]}
              promotedBulkActions={promotedBulkActions}
            >
              {rowMarkup}
            </IndexTable>
          </Card>
        </Layout.Section>

        <Layout.Section variant="oneThird">
          <Card>
            <BlockStack gap="400">
              <Text as="h2" variant="headingMd">Quick Stats</Text>
              <Divider />
              <BlockStack gap="200">
                <InlineStack align="space-between">
                  <Text tone="subdued">Total products</Text>
                  <Text fontWeight="semibold">{products.length}</Text>
                </InlineStack>
                <InlineStack align="space-between">
                  <Text tone="subdued">Active</Text>
                  <Text fontWeight="semibold">
                    {products.filter(p => p.status === 'active').length}
                  </Text>
                </InlineStack>
              </BlockStack>
            </BlockStack>
          </Card>

          <Box paddingBlockStart="400">
            <Card>
              <BlockStack gap="300">
                <Text as="h3" variant="headingSm">Quick Actions</Text>
                <Button variant="plain" url="/settings">
                  View all settings
                </Button>
              </BlockStack>
            </Card>
          </Box>
        </Layout.Section>
      </Layout>
    </Page>
  );
}

Anti-Patterns to AVOID

需要避免的反模式

  • DO NOT use deprecated components (
    LegacyCard
    ,
    Modal
    ,
    Toast
    , etc.).
  • DO NOT use Shadows or Borders manually. Cards handle this.
  • DO NOT use
    style={{ margin: 10 }}
    . Use
    <Box padding="400">
    or
    <BlockStack gap="400">
    .
  • DO NOT create a "Save" button at the bottom of a form. Use App Bridge Save Bar.
  • DO NOT use generic loading spinners. Use
    <SkeletonPage>
    or
    <SkeletonBodyText>
    for loading states.
  • DO NOT use
    <h1>
    ,
    <h2>
    , etc. directly. Use
    <Text as="h2" variant="headingMd">
    .
  • DO NOT use inline styles. Use Box/BlockStack props or design tokens.
  • DO NOT import from
    @shopify/polaris/build/esm/...
    . Use named exports from
    @shopify/polaris
    .
  • 不要使用已废弃的组件(
    LegacyCard
    Modal
    Toast
    等)。
  • 不要手动设置阴影或边框,Card组件会自动处理。
  • 不要使用
    style={{ margin: 10 }}
    ,请使用
    <Box padding="400">
    <BlockStack gap="400">
  • 不要在表单底部创建“保存”按钮,请使用App Bridge保存栏。
  • 不要使用通用加载 spinner,请使用
    <SkeletonPage>
    <SkeletonBodyText>
    实现加载状态。
  • 不要直接使用
    <h1>
    <h2>
    等原生标题标签,请使用
    <Text as="h2" variant="headingMd">
  • 不要使用内联样式,请使用Box/BlockStack属性或设计令牌。
  • 不要
    @shopify/polaris/build/esm/...
    导入组件,请使用
    @shopify/polaris
    的命名导出。

Internationalization Support (v13.10+)

国际化支持(v13.10+)

Polaris v13.10 added translations for 8 new languages: Hindi, Lithuanian, Bulgarian, Hungarian, Romanian, Russian, Indonesian, and Greek.
jsx
import enTranslations from '@shopify/polaris/locales/en.json';
import viTranslations from '@shopify/polaris/locales/vi.json';

// Use the appropriate translations based on merchant locale
<AppProvider i18n={merchantLocale === 'vi' ? viTranslations : enTranslations}>
  <App />
</AppProvider>
Polaris v13.10新增了8种语言的翻译:印地语、立陶宛语、保加利亚语、匈牙利语、罗马尼亚语、俄语、印度尼西亚语和希腊语。
jsx
import enTranslations from '@shopify/polaris/locales/en.json';
import viTranslations from '@shopify/polaris/locales/vi.json';

// 根据商家的地区设置使用对应翻译
<AppProvider i18n={merchantLocale === 'vi' ? viTranslations : enTranslations}>
  <App />
</AppProvider>

Resources

参考资源