wix-cli-site-widget

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Wix Site Widget Builder

Wix Site Widget Builder

Creates custom element widget extensions for Wix CLI applications. Site widgets are React components converted to web components that appear in the Wix Editor, allowing site owners to add interactive, configurable widgets to their pages with a built-in settings panel.
为Wix CLI应用创建自定义元素小组件扩展。站点小组件是转换为Web组件的React组件,可在Wix编辑器中显示,允许站点所有者通过内置设置面板向其页面添加交互式、可配置的小组件。

Quick Start Checklist

快速开始清单

Follow these steps in order when creating a site widget:
  1. Create widget folder:
    src/site/widgets/custom-elements/<widget-name>/
  2. Create
    widget.tsx
    with React component and
    reactToWebComponent
    conversion
  3. Create
    panel.tsx
    with WDS components and
    widget.getProp/setProp
  4. Create
    extensions.ts
    with
    extensions.customElement()
    and unique UUID
  5. Update
    src/extensions.ts
    to import and use the new extension
创建站点小组件时,请按以下顺序执行步骤:
  1. 创建小组件文件夹:
    src/site/widgets/custom-elements/<widget-name>/
  2. 创建包含React组件和
    reactToWebComponent
    转换逻辑的
    widget.tsx
  3. 创建包含WDS组件和
    widget.getProp/setProp
    panel.tsx
  4. 创建包含
    extensions.customElement()
    和唯一UUID的
    extensions.ts
  5. 更新
    src/extensions.ts
    以导入并使用新扩展

Architecture

架构

Site widgets consist of two required files:
站点小组件包含两个必填文件

1. Widget Component (
widget.tsx
)

1. 小组件组件(
widget.tsx

React component converted to a web component using
react-to-webcomponent
:
  • Define Props interface with configurable properties (camelCase)
  • Create a React functional component that renders the widget UI
  • Convert to web component with props mapping
  • Use inline styles (no CSS imports)
  • Handle Wix Editor environment when using Wix Data API
使用
react-to-webcomponent
转换为Web组件的React组件:
  • 定义包含可配置属性的Props接口(小驼峰命名)
  • 创建渲染小组件UI的React函数式组件
  • 转换为带属性映射的Web组件
  • 使用内联样式(禁止CSS导入)
  • 使用Wix Data API时需适配Wix编辑器环境

2. Settings Panel (
panel.tsx
)

2. 设置面板(
panel.tsx

Settings panel shown in the Wix Editor sidebar:
  • Uses Wix Design System components (see references/SETTINGS_PANEL.md)
  • Manages widget properties via
    @wix/editor
    widget API
  • Loads initial values with
    widget.getProp('kebab-case-name')
  • Updates properties with
    widget.setProp('kebab-case-name', value)
  • Wrapped in
    WixDesignSystemProvider > SidePanel > SidePanel.Content
在Wix编辑器侧边栏显示的设置面板:
  • 使用Wix设计系统组件(参见references/SETTINGS_PANEL.md
  • 通过
    @wix/editor
    小组件API管理小组件属性
  • 使用
    widget.getProp('kebab-case-name')
    加载初始值
  • 使用
    widget.setProp('kebab-case-name', value)
    更新属性
  • 包裹在
    WixDesignSystemProvider > SidePanel > SidePanel.Content

Widget Component Pattern

小组件组件模式

typescript
import React, { type FC, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import reactToWebComponent from "react-to-webcomponent";

interface WidgetProps {
  title?: string;
  targetDate?: string;
  bgColor?: string;
  textColor?: string;
  font?: string;
}

const CustomElement: FC<WidgetProps> = ({
  title = "Default Title",
  targetDate = "",
  bgColor = "#ffffff",
  textColor = "#333333",
  font = "{}",
}) => {
  // Parse font if needed
  const { font: textFont, textDecoration } = JSON.parse(font);

  // Component logic and state
  const [data, setData] = useState(null);

  // Use inline styles
  const styles = {
    wrapper: {
      display: "flex",
      flexDirection: "column",
      padding: "20px",
      backgroundColor: bgColor,
      color: textColor,
      fontFamily: textFont || "inherit",
    },
  };

  return (
    <div style={styles.wrapper}>
      {title && <h2 style={{ margin: 0 }}>{title}</h2>}
      {/* Widget content */}
    </div>
  );
};

// Convert to web component
const customElement = reactToWebComponent(CustomElement, React, ReactDOM, {
  props: {
    title: "string",
    targetDate: "string",
    bgColor: "string",
    textColor: "string",
    font: "string",
  },
});

export default customElement;
Key Points:
  • Props interface uses camelCase (e.g.,
    targetDate
    ,
    bgColor
    )
  • reactToWebComponent
    config uses camelCase keys with
    'string'
    type
  • All props are passed as strings from the web component
  • Use inline styles, not CSS imports
  • Parse complex props (like
    font
    ) from JSON strings if needed
typescript
import React, { type FC, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import reactToWebComponent from "react-to-webcomponent";

interface WidgetProps {
  title?: string;
  targetDate?: string;
  bgColor?: string;
  textColor?: string;
  font?: string;
}

const CustomElement: FC<WidgetProps> = ({
  title = "Default Title",
  targetDate = "",
  bgColor = "#ffffff",
  textColor = "#333333",
  font = "{}",
}) => {
  // Parse font if needed
  const { font: textFont, textDecoration } = JSON.parse(font);

  // Component logic and state
  const [data, setData] = useState(null);

  // Use inline styles
  const styles = {
    wrapper: {
      display: "flex",
      flexDirection: "column",
      padding: "20px",
      backgroundColor: bgColor,
      color: textColor,
      fontFamily: textFont || "inherit",
    },
  };

  return (
    <div style={styles.wrapper}>
      {title && <h2 style={{ margin: 0 }}>{title}</h2>}
      {/* Widget content */}
    </div>
  );
};

// Convert to web component
const customElement = reactToWebComponent(CustomElement, React, ReactDOM, {
  props: {
    title: "string",
    targetDate: "string",
    bgColor: "string",
    textColor: "string",
    font: "string",
  },
});

export default customElement;
核心要点:
  • Props接口使用小驼峰命名(例如:
    targetDate
    ,
    bgColor
  • reactToWebComponent
    配置使用小驼峰键和
    'string'
    类型
  • 所有属性从Web组件以字符串形式传递
  • 使用内联样式,而非CSS导入
  • 必要时从JSON字符串解析复杂属性(如
    font

Settings Panel Pattern

设置面板模式

typescript
import React, { type FC, useState, useEffect, useCallback } from "react";
import { widget } from "@wix/editor";
import {
  SidePanel,
  WixDesignSystemProvider,
  Input,
  FormField,
  Box,
} from "@wix/design-system";
import "@wix/design-system/styles.global.css";

const Panel: FC = () => {
  const [title, setTitle] = useState<string>("");
  const [targetDate, setTargetDate] = useState<string>("");
  const [bgColor, setBgColor] = useState<string>("#ffffff");

  // Load initial values (kebab-case prop names)
  useEffect(() => {
    Promise.all([
      widget.getProp("title"),
      widget.getProp("target-date"),
      widget.getProp("bg-color"),
    ])
      .then(([titleVal, dateVal, bgColorVal]) => {
        setTitle(titleVal || "");
        setTargetDate(dateVal || "");
        setBgColor(bgColorVal || "#ffffff");
      })
      .catch((error) =>
        console.error("Failed to fetch widget properties:", error)
      );
  }, []);

  // Update both local state and widget prop (kebab-case)
  const handleTitleChange = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const newTitle = event.target.value;
      setTitle(newTitle);
      widget.setProp("title", newTitle);
    },
    []
  );

  const handleDateChange = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const newDate = event.target.value;
      setTargetDate(newDate);
      widget.setProp("target-date", newDate);
    },
    []
  );

  return (
    <WixDesignSystemProvider>
      <SidePanel width="300" height="100vh">
        <SidePanel.Header title="Widget Settings" />
        <SidePanel.Content noPadding stretchVertically>
          <Box direction="vertical" gap="24px">
            <SidePanel.Field>
              <FormField label="Title">
                <Input
                  type="text"
                  value={title}
                  onChange={handleTitleChange}
                  placeholder="Enter title"
                />
              </FormField>
            </SidePanel.Field>

            <SidePanel.Field>
              <FormField label="Target Date">
                <Input
                  type="date"
                  value={targetDate}
                  onChange={handleDateChange}
                />
              </FormField>
            </SidePanel.Field>
          </Box>
        </SidePanel.Content>
      </SidePanel>
    </WixDesignSystemProvider>
  );
};

export default Panel;
Key Points:
  • Prop names in
    widget.getProp()
    and
    widget.setProp()
    use kebab-case (e.g.,
    "target-date"
    ,
    "bg-color"
    )
  • Always update both local state AND widget prop in onChange handlers
  • Wrap content in
    WixDesignSystemProvider > SidePanel > SidePanel.Content
  • Use WDS components from
    @wix/design-system
    (see references/SETTINGS_PANEL.md)
  • Import
    @wix/design-system/styles.global.css
    for styles
typescript
import React, { type FC, useState, useEffect, useCallback } from "react";
import { widget } from "@wix/editor";
import {
  SidePanel,
  WixDesignSystemProvider,
  Input,
  FormField,
  Box,
} from "@wix/design-system";
import "@wix/design-system/styles.global.css";

const Panel: FC = () => {
  const [title, setTitle] = useState<string>("");
  const [targetDate, setTargetDate] = useState<string>("");
  const [bgColor, setBgColor] = useState<string>("#ffffff");

  // Load initial values (kebab-case prop names)
  useEffect(() => {
    Promise.all([
      widget.getProp("title"),
      widget.getProp("target-date"),
      widget.getProp("bg-color"),
    ])
      .then(([titleVal, dateVal, bgColorVal]) => {
        setTitle(titleVal || "");
        setTargetDate(dateVal || "");
        setBgColor(bgColorVal || "#ffffff");
      })
      .catch((error) =>
        console.error("Failed to fetch widget properties:", error)
      );
  }, []);

  // Update both local state and widget prop (kebab-case)
  const handleTitleChange = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const newTitle = event.target.value;
      setTitle(newTitle);
      widget.setProp("title", newTitle);
    },
    []
  );

  const handleDateChange = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const newDate = event.target.value;
      setTargetDate(newDate);
      widget.setProp("target-date", newDate);
    },
    []
  );

  return (
    <WixDesignSystemProvider>
      <SidePanel width="300" height="100vh">
        <SidePanel.Header title="Widget Settings" />
        <SidePanel.Content noPadding stretchVertically>
          <Box direction="vertical" gap="24px">
            <SidePanel.Field>
              <FormField label="Title">
                <Input
                  type="text"
                  value={title}
                  onChange={handleTitleChange}
                  placeholder="Enter title"
                />
              </FormField>
            </SidePanel.Field>

            <SidePanel.Field>
              <FormField label="Target Date">
                <Input
                  type="date"
                  value={targetDate}
                  onChange={handleDateChange}
                />
              </FormField>
            </SidePanel.Field>
          </Box>
        </SidePanel.Content>
      </SidePanel>
    </WixDesignSystemProvider>
  );
};

export default Panel;
核心要点:
  • widget.getProp()
    widget.setProp()
    中的属性名称使用短横线命名(例如:
    "target-date"
    ,
    "bg-color"
  • 在onChange处理函数中始终同时更新本地状态和小组件属性
  • 将内容包裹在
    WixDesignSystemProvider > SidePanel > SidePanel.Content
  • 使用
    @wix/design-system
    中的WDS组件(参见references/SETTINGS_PANEL.md
  • 导入
    @wix/design-system/styles.global.css
    以应用样式

Props Naming Convention

属性命名规范

Critical: Props use different naming conventions in each file:
FileConventionExample
widget.tsx
(Props interface)
camelCase
targetDate
,
bgColor
,
textColor
panel.tsx
(widget API)
kebab-case
"target-date"
,
"bg-color"
,
"text-color"
reactToWebComponent
config
camelCase
targetDate: 'string'
The web component automatically converts between camelCase (React props) and kebab-case (HTML attributes).
重要提示: 各文件中属性使用不同的命名规范:
文件命名规范示例
widget.tsx
(Props接口)
小驼峰
targetDate
,
bgColor
,
textColor
panel.tsx
(小组件API)
短横线
"target-date"
,
"bg-color"
,
"text-color"
reactToWebComponent
配置
小驼峰
targetDate: 'string'
Web组件会自动在小驼峰(React属性)和短横线(HTML属性)之间进行转换。

Wix Data API Integration

Wix Data API集成

When using Wix Data API in widgets, you must handle the Wix Editor environment gracefully:
typescript
import { items } from "@wix/data";
import { window as wixWindow } from "@wix/site-window";

const CustomElement: FC<WidgetProps> = ({ collectionId }) => {
  const [data, setData] = useState(null);
  const [isEditor, setIsEditor] = useState(false);

  useEffect(() => {
    const loadData = async () => {
      const currentViewMode = await wixWindow.viewMode();

      if (currentViewMode === "Editor") {
        // Don't fetch data in editor - show placeholder
        setIsEditor(true);
        return;
      }

      // Fetch real data only on live site
      try {
        const results = await items.query(collectionId).limit(10).find();
        setData(results.items);
      } catch (error) {
        console.error("Failed to load data:", error);
      }
    };

    loadData();
  }, [collectionId]);

  if (isEditor) {
    return (
      <div style={{ padding: "20px", border: "2px dashed #ccc" }}>
        <p>Widget will display data on the live site</p>
        <p>Collection: {collectionId}</p>
      </div>
    );
  }

  // Render widget with real data
  return (
    <div>
      {data?.map((item) => (
        <div key={item._id}>{item.title}</div>
      ))}
    </div>
  );
};
Requirements:
  • Import
    { window as wixWindow }
    from
    "@wix/site-window"
  • Check
    await wixWindow.viewMode()
    before fetching data
  • If
    viewMode === 'Editor'
    , show a placeholder UI instead
  • Only fetch and render real data when NOT in editor mode
在小组件中使用Wix Data API时,您必须优雅适配Wix编辑器环境:
typescript
import { items } from "@wix/data";
import { window as wixWindow } from "@wix/site-window";

const CustomElement: FC<WidgetProps> = ({ collectionId }) => {
  const [data, setData] = useState(null);
  const [isEditor, setIsEditor] = useState(false);

  useEffect(() => {
    const loadData = async () => {
      const currentViewMode = await wixWindow.viewMode();

      if (currentViewMode === "Editor") {
        // Don't fetch data in editor - show placeholder
        setIsEditor(true);
        return;
      }

      // Fetch real data only on live site
      try {
        const results = await items.query(collectionId).limit(10).find();
        setData(results.items);
      } catch (error) {
        console.error("Failed to load data:", error);
      }
    };

    loadData();
  }, [collectionId]);

  if (isEditor) {
    return (
      <div style={{ padding: "20px", border: "2px dashed #ccc" }}>
        <p>Widget will display data on the live site</p>
        <p>Collection: {collectionId}</p>
      </div>
    );
  }

  // Render widget with real data
  return (
    <div>
      {data?.map((item) => (
        <div key={item._id}>{item.title}</div>
      ))}
    </div>
  );
};
要求:
  • "@wix/site-window"
    导入
    { window as wixWindow }
  • 在获取数据前检查
    await wixWindow.viewMode()
  • 如果
    viewMode === 'Editor'
    ,则显示占位UI而非真实数据
  • 仅在非编辑器模式下获取并渲染真实数据

Font Selection

字体选择

For font selection in settings panels, use
FontPickerField
component with
inputs.selectFont()
:
typescript
import { inputs } from "@wix/editor";
import { FontPickerField } from "./components/FontPickerField";

const Panel: FC = () => {
  const [font, setFont] = useState({ font: "", textDecoration: "" });

  const handleFontChange = async () => {
    const selectedFont = await inputs.selectFont();
    if (selectedFont) {
      const fontValue = {
        font: selectedFont.fontFamily || "",
        textDecoration: selectedFont.textDecoration || "",
      };
      setFont(fontValue);
      widget.setProp("font", JSON.stringify(fontValue));
    }
  };

  return (
    <FontPickerField
      label="Text Font"
      value={font}
      onChange={handleFontChange}
    />
  );
};
Important: Use
inputs.selectFont()
from
@wix/editor
, NOT a text Input. This provides a rich font picker dialog with bold, italic, size, and typography features.
在设置面板中选择字体时,使用
FontPickerField
组件和
inputs.selectFont()
typescript
import { inputs } from "@wix/editor";
import { FontPickerField } from "./components/FontPickerField";

const Panel: FC = () => {
  const [font, setFont] = useState({ font: "", textDecoration: "" });

  const handleFontChange = async () => {
    const selectedFont = await inputs.selectFont();
    if (selectedFont) {
      const fontValue = {
        font: selectedFont.fontFamily || "",
        textDecoration: selectedFont.textDecoration || "",
      };
      setFont(fontValue);
      widget.setProp("font", JSON.stringify(fontValue));
    }
  };

  return (
    <FontPickerField
      label="Text Font"
      value={font}
      onChange={handleFontChange}
    />
  );
};
重要提示: 使用
@wix/editor
中的
inputs.selectFont()
,而非文本输入框。这将提供一个功能丰富的字体选择对话框,包含加粗、斜体、字号和排版功能。

Output Structure

输出结构

src/site/widgets/custom-elements/
└── {widget-name}/
    ├── widget.tsx           # Main widget component
    ├── panel.tsx            # Settings panel component
    ├── extensions.ts         # Extension registration
    ├── components/          # Optional sub-components
    │   ├── ColorPickerField.tsx
    │   └── FontPickerField.tsx
    └── utils/               # Optional helper functions
        └── formatters.ts
src/site/widgets/custom-elements/
└── {widget-name}/
    ├── widget.tsx           # Main widget component
    ├── panel.tsx            # Settings panel component
    ├── extensions.ts         # Extension registration
    ├── components/          # Optional sub-components
    │   ├── ColorPickerField.tsx
    │   └── FontPickerField.tsx
    └── utils/               # Optional helper functions
        └── formatters.ts

Examples

示例

Countdown Timer Widget

倒计时器小组件

Request: "Create a countdown timer widget"
Output:
  • Widget with configurable title, target date/time, colors, and font
  • Settings panel with date picker, time input, color pickers, font picker
  • Real-time countdown display with days, hours, minutes, seconds
需求: "创建一个倒计时器小组件"
输出:
  • 带可配置标题、目标日期/时间、颜色和字体的小组件
  • 包含日期选择器、时间输入框、颜色选择器、字体选择器的设置面板
  • 显示天、时、分、秒的实时倒计时

Product Showcase Widget

产品展示小组件

Request: "Create a widget that displays products from a collection"
Output:
  • Widget that queries Wix Data collection
  • Editor environment handling (shows placeholder in editor)
  • Settings panel for collection selection, display options, styling
  • Responsive grid layout with product cards
需求: "创建一个显示集合中产品的小组件"
输出:
  • 查询Wix Data集合的小组件
  • 编辑器环境适配(在编辑器中显示占位符)
  • 用于集合选择、显示选项、样式设置的面板
  • 带产品卡片的响应式网格布局

Interactive Calculator Widget

交互式计算器小组件

Request: "Create a calculator widget with customizable colors"
Output:
  • Functional calculator component
  • Settings panel for color customization (background, buttons, text)
  • Inline styles for all styling
  • No external dependencies
需求: "创建一个可自定义颜色的计算器小组件"
输出:
  • 功能完整的计算器组件
  • 用于颜色自定义(背景、按钮、文本)的设置面板
  • 所有样式均使用内联样式
  • 无外部依赖

Frontend Aesthetics

前端美学

Avoid generic aesthetics. Create distinctive designs with unique fonts (avoid Inter, Roboto, Arial), cohesive color palettes, CSS animations for micro-interactions, and context-specific choices. Don't use clichéd color schemes or predictable layouts.
避免通用化的美学设计。创建具有独特字体(避免Inter、Roboto、Arial)、协调调色板、微交互CSS动画和特定上下文选择的独特设计。不要使用陈词滥调的配色方案或可预测的布局。

Extension Registration

扩展注册

Extension registration is MANDATORY and has TWO required steps.
扩展注册是强制性的,包含两个必填步骤。

Step 1: Create Widget-Specific Extension File

步骤1:创建小组件专属扩展文件

Each site widget requires an
extensions.ts
file in its folder:
typescript
import { extensions } from "@wix/astro/builders";

export const sitewidgetMyWidget = extensions.customElement({
  id: "{{GENERATE_UUID}}",
  name: "My Widget",
  tagName: "my-widget",
  element: "./site/widgets/custom-elements/my-widget/widget.tsx",
  settings: "./site/widgets/custom-elements/my-widget/panel.tsx",
  installation: {
    autoAdd: true,
  },
  width: {
    defaultWidth: 500,
    allowStretch: true,
  },
  height: {
    defaultHeight: 500,
  },
});
CRITICAL: UUID Generation
The
id
must be a unique, static UUID v4 string. Generate a fresh UUID for each extension - do NOT use
randomUUID()
or copy UUIDs from examples. Replace
{{GENERATE_UUID}}
with a freshly generated UUID like
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
.
PropertyTypeDescription
id
stringUnique static UUID v4 (generate fresh)
name
stringDisplay name in editor
tagName
stringHTML custom element tag (kebab-case)
element
stringPath to widget React component
settings
stringPath to settings panel component
installation
objectAuto-add behavior
width
objectDefault width and stretch settings
height
objectDefault height settings
每个站点小组件在其文件夹中都需要一个
extensions.ts
文件:
typescript
import { extensions } from "@wix/astro/builders";

export const sitewidgetMyWidget = extensions.customElement({
  id: "{{GENERATE_UUID}}",
  name: "My Widget",
  tagName: "my-widget",
  element: "./site/widgets/custom-elements/my-widget/widget.tsx",
  settings: "./site/widgets/custom-elements/my-widget/panel.tsx",
  installation: {
    autoAdd: true,
  },
  width: {
    defaultWidth: 500,
    allowStretch: true,
  },
  height: {
    defaultHeight: 500,
  },
});
重要提示:UUID生成
id
必须是唯一的静态UUID v4字符串。为每个扩展生成新的UUID - 请勿使用
randomUUID()
或复制示例中的UUID。将
{{GENERATE_UUID}}
替换为新生成的UUID,例如
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
属性类型描述
id
string唯一的静态UUID v4(需新生成)
name
string编辑器中的显示名称
tagName
stringHTML自定义元素标签(短横线命名)
element
string小组件React组件的路径
settings
string设置面板组件的路径
installation
object自动添加行为
width
object默认宽度和拉伸设置
height
object默认高度设置

Step 2: Register in Main Extensions File

步骤2:在主扩展文件中注册

CRITICAL: After creating the widget-specific extension file, you MUST read wix-cli-extension-registration and follow the "App Registration" section to update
src/extensions.ts
.
Without completing Step 2, the site widget will not be available in the Wix Editor.
重要提示: 创建小组件专属扩展文件后,您必须阅读wix-cli-extension-registration并按照“应用注册”部分的说明更新
src/extensions.ts
未完成步骤2,站点小组件将无法在Wix编辑器中使用。

Code Quality Requirements

代码质量要求

  • Strict TypeScript (no
    any
    , explicit return types)
  • Functional React components with hooks
  • Proper error handling and loading states
  • No
    @ts-ignore
    comments
  • Inline styles only (no CSS imports)
  • Handle Wix Editor environment when using Wix Data API
  • Consistent prop naming (camelCase in widget, kebab-case in panel)
  • 严格的TypeScript(禁止
    any
    ,显式返回类型)
  • 使用Hooks的React函数式组件
  • 适当的错误处理和加载状态
  • 禁止
    @ts-ignore
    注释
  • 仅使用内联样式(禁止CSS导入)
  • 使用Wix Data API时适配Wix编辑器环境
  • 一致的属性命名(小组件中使用小驼峰,面板中使用短横线)

Verification

验证

After implementation completes, the wix-cli-orchestrator will run validation using wix-cli-app-validation.
实现完成后,wix-cli-orchestrator将使用wix-cli-app-validation运行验证。