npx skills add https://github.com/wix/skills --skill wix-cli-site-widget为 Wix CLI 应用程序创建自定义元素小部件扩展。站点小部件是转换为 Web 组件的 React 组件,它们会出现在 Wix 编辑器中,允许站点所有者通过内置的设置面板将交互式、可配置的小部件添加到他们的页面。
创建站点小部件时,请按顺序遵循以下步骤:
src/extensions/site/widgets/custom-elements/<widget-name>/reactToWebComponent 转换创建 widget.tsxwidget.getProp/setProp 创建 panel.tsxextensions.customElement() 和唯一的 UUID 创建 extensions.tssrc/extensions.ts 以导入并使用新的扩展广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
站点小部件包含 两个必需的文件:
widget.tsx)使用 react-to-webcomponent 转换为 Web 组件的 React 组件:
panel.tsx)在 Wix 编辑器侧边栏中显示设置面板:
@wix/editor 小部件 API 管理小部件属性widget.getProp('kebab-case-name') 加载初始值widget.setProp('kebab-case-name', value) 更新属性WixDesignSystemProvider > SidePanel > SidePanel.Content 中@wix/editor 的 inputs.selectColor() 和 FillPreview — 不要使用 <Input type="color">@wix/editor 的 inputs.selectFont() 和一个 Button — 不要使用文本 Inputimport React, { type FC, useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import reactToWebComponent from 'react-to-webcomponent';
interface WidgetProps {
title?: string;
targetDate?: string;
targetTime?: string;
bgColor?: string;
textColor?: string;
font?: string;
}
const CustomElement: FC<WidgetProps> = ({
title = 'Countdown',
targetDate = '',
targetTime = '00:00',
bgColor = '#ffffff',
textColor = '#333333',
font = "{}",
}) => {
const { font: textFont, textDecoration } = JSON.parse(font);
const [time, setTime] = useState({ days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: false });
useEffect(() => {
if (!targetDate) return;
const update = () => {
const target = new Date(`${targetDate}T${targetTime}`);
const diff = target.getTime() - Date.now();
if (diff <= 0) {
setTime({ days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: true });
return;
}
setTime({
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
hours: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
seconds: Math.floor((diff % (1000 * 60)) / 1000),
isExpired: false,
});
};
update();
const interval = setInterval(update, 1000);
return () => clearInterval(interval);
}, [targetDate, targetTime]);
const styles = {
wrapper: {
backgroundColor: bgColor,
padding: '24px 32px',
textAlign: 'center' as const,
display: 'inline-block',
},
title: {
font: textFont || '600 24px sans-serif',
color: textColor,
textDecoration,
marginBottom: '16px',
},
};
return (
<div style={styles.wrapper}>
{title && <div style={styles.title}>{title}</div>}
{/* Widget content */}
</div>
);
};
const customElement = reactToWebComponent(CustomElement, React, ReactDOM, {
props: {
title: 'string',
targetDate: 'string',
targetTime: 'string',
bgColor: 'string',
textColor: 'string',
font: 'string',
},
});
export default customElement;
关键点:
targetDate、bgColor)reactToWebComponent 配置使用驼峰命名法的键和 'string' 类型font):const { font: textFont, textDecoration } = JSON.parse(font)font CSS 简写属性和 textDecoration 属性应用字体import React, { type FC, useState, useEffect, useCallback } from "react";
import { widget } from "@wix/editor";
import {
SidePanel,
WixDesignSystemProvider,
Input,
FormField,
TimeInput,
Box,
} from "@wix/design-system";
import "@wix/design-system/styles.global.css";
import { ColorPickerField } from "./components/ColorPickerField";
import { FontPickerField } from "./components/FontPickerField";
import { parseTimeValue } from "./utils";
const DEFAULT_BG_COLOR = "#0a0e27";
const DEFAULT_TEXT_COLOR = "#00ff88";
const DEFAULT_TEXT_FONT = "";
const DEFAULT_TEXT_DECORATION = "";
const Panel: FC = () => {
const [title, setTitle] = useState<string>("Countdown");
const [targetDate, setTargetDate] = useState<string>("");
const [targetTime, setTargetTime] = useState<string>("00:00");
const [bgColor, setBgColor] = useState<string>(DEFAULT_BG_COLOR);
const [textColor, setTextColor] = useState<string>(DEFAULT_TEXT_COLOR);
const [font, setFont] = useState({ font: DEFAULT_TEXT_FONT, textDecoration: DEFAULT_TEXT_DECORATION });
useEffect(() => {
Promise.all([
widget.getProp("title"),
widget.getProp("target-date"),
widget.getProp("target-time"),
widget.getProp("bg-color"),
widget.getProp("text-color"),
widget.getProp("font"),
])
.then(([titleVal, dateVal, timeVal, bgColorVal, textColorVal, fontString]) => {
setTitle(titleVal || "Countdown");
setTargetDate(dateVal || "");
setTargetTime(timeVal || "00:00");
setBgColor(bgColorVal || DEFAULT_BG_COLOR);
setTextColor(textColorVal || DEFAULT_TEXT_COLOR);
setFont(JSON.parse(fontString || "{}"));
})
.catch((error) => console.error("Failed to fetch widget properties:", error));
}, []);
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);
}, []);
const handleTimeChange = useCallback(({ date }: { date: Date }) => {
if (date) {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const newTime = `${hours}:${minutes}`;
setTargetTime(newTime);
widget.setProp("target-time", newTime);
}
}, []);
const handleBgColorChange = (value: string) => {
setBgColor(value);
widget.setProp("bg-color", value);
};
const handleTextColorChange = (value: string) => {
setTextColor(value);
widget.setProp("text-color", value);
};
const handleFontChange = (value: { font: string; textDecoration: string }) => {
setFont(value);
widget.setProp("font", JSON.stringify(value));
};
return (
<WixDesignSystemProvider>
<SidePanel width="300" height="100vh">
<SidePanel.Header title="Countdown Settings" />
<SidePanel.Content noPadding stretchVertically>
<Box direction="vertical" gap="24px">
<SidePanel.Field>
<FormField label="Title" required>
<Input
type="text"
value={title}
onChange={handleTitleChange}
placeholder="Enter countdown title"
/>
</FormField>
</SidePanel.Field>
<SidePanel.Field>
<FormField label="Target Date" required>
<Input
type="date"
value={targetDate}
onChange={handleDateChange}
/>
</FormField>
</SidePanel.Field>
<SidePanel.Field>
<FormField label="Target Time" required>
<TimeInput
value={parseTimeValue(targetTime)}
onChange={handleTimeChange}
/>
</FormField>
</SidePanel.Field>
<ColorPickerField
label="Background Color"
value={bgColor}
onChange={handleBgColorChange}
/>
<ColorPickerField
label="Text Color"
value={textColor}
onChange={handleTextColorChange}
/>
<FontPickerField
label="Text Font"
value={font}
onChange={handleFontChange}
/>
</Box>
</SidePanel.Content>
</SidePanel>
</WixDesignSystemProvider>
);
};
export default Panel;
关键点:
widget.getProp() 和 widget.setProp() 中的属性名称使用 短横线命名法(例如,"target-date"、"bg-color")WixDesignSystemProvider > SidePanel > SidePanel.Content 中@wix/design-system 的 WDS 组件(参见 references/SETTINGS_PANEL.md)@wix/design-system/styles.global.css 以获取样式@wix/editor 的 inputs.selectColor() 的 ColorPickerField — 不要使用 <Input type="color">@wix/editor 的 inputs.selectFont() 的 FontPickerField — 不要使用文本 InputJSON.stringify() / JSON.parse() 存储为 JSON 字符串关键: 每个文件中的属性使用不同的命名约定:
| 文件 | 约定 | 示例 |
|---|---|---|
widget.tsx (Props 接口) | 驼峰命名法 | targetDate, bgColor, textColor |
panel.tsx (widget API) | 短横线命名法 | "target-date", "bg-color", "text-color" |
reactToWebComponent 配置 | 驼峰命名法 | targetDate: 'string' |
Web 组件会自动在驼峰命名法(React 属性)和短横线命名法(HTML 属性)之间进行转换。
在小部件中使用 Wix Data API 时,您必须优雅地处理 Wix 编辑器环境:
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对于设置面板中的颜色选择,请使用带有来自 @wix/editor 的 inputs.selectColor() 的 ColorPickerField 组件。不要使用 <Input type="color">。
// components/ColorPickerField.tsx
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Box, FillPreview, SidePanel } from '@wix/design-system';
interface ColorPickerFieldProps {
label: string;
value: string;
onChange: (value: string) => void;
}
export const ColorPickerField: FC<ColorPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Box width="30px" height="30px">
<FillPreview
fill={value}
onClick={() => inputs.selectColor(value, { onChange: (val) => { if (val) onChange(val); } })}
/>
</Box>
</FormField>
</SidePanel.Field>
);
在面板中的用法:
const handleBgColorChange = (value: string) => {
setBgColor(value);
widget.setProp("bg-color", value);
};
<ColorPickerField label="Background Color" value={bgColor} onChange={handleBgColorChange} />
重要: 使用来自 @wix/editor 的 inputs.selectColor(value, { onChange }) 和来自 WDS 的 FillPreview。这将打开原生的 Wix 颜色选择器,包含主题颜色、渐变等。永远不要使用 <Input type="color">。
对于设置面板中的字体选择,请使用带有来自 @wix/editor 的 inputs.selectFont() 的 FontPickerField 组件。不要使用文本 Input。
// components/FontPickerField.tsx
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Button, Text, SidePanel } from '@wix/design-system';
interface FontValue {
font: string;
textDecoration: string;
}
interface FontPickerFieldProps {
label: string;
value: FontValue;
onChange: (value: FontValue) => void;
}
export const FontPickerField: FC<FontPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Button
size="small"
priority="secondary"
onClick={() => inputs.selectFont(value, { onChange: (val) => onChange({ font: val.font, textDecoration: val.textDecoration || "" }) })}
fullWidth
>
<Text size="small" ellipsis>Change Font</Text>
</Button>
</FormField>
</SidePanel.Field>
);
在面板中的用法:
const [font, setFont] = useState<FontValue>({ font: "", textDecoration: "" });
const handleFontChange = (value: FontValue) => {
setFont(value);
widget.setProp("font", JSON.stringify(value));
};
<FontPickerField label="Text Font" value={font} onChange={handleFontChange} />
重要: 使用来自 @wix/editor 的 inputs.selectFont(value, { onChange }) 和回调模式。这提供了一个丰富的字体选择器对话框,包含粗体、斜体、大小和排版功能。字体值存储为 JSON 字符串。
src/extensions/site/widgets/custom-elements/
└── {widget-name}/
├── widget.tsx # 主小部件组件
├── panel.tsx # 设置面板组件
├── extensions.ts # 扩展注册
├── components/ # 可选的子组件
│ ├── ColorPickerField.tsx
│ └── FontPickerField.tsx
└── utils/ # 可选的辅助函数
└── formatters.ts
请求: "创建一个倒计时器小部件"
输出:
请求: "创建一个显示集合中产品的小部件"
输出:
请求: "创建一个具有可自定义颜色的计算器小部件"
输出:
避免通用美学。创建具有独特字体(避免使用 Inter、Roboto、Arial)、协调的调色板、用于微交互的 CSS 动画以及特定上下文选择的独特设计。不要使用陈词滥调的色彩方案或可预测的布局。
扩展注册是强制性的,并且需要两个必需的步骤。
每个站点小部件都需要在其文件夹中有一个 extensions.ts 文件:
import { extensions } from "@wix/astro/builders";
export const sitewidgetMyWidget = extensions.customElement({
id: "{{GENERATE_UUID}}",
name: "My Widget",
tagName: "my-widget",
element: "./extensions/site/widgets/custom-elements/my-widget/widget.tsx",
settings: "./extensions/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 | string | HTML 自定义元素标签(短横线命名法) |
element | string | 小部件 React 组件的路径 |
settings | string | 设置面板组件的路径 |
installation | object | 自动添加行为 |
width | object | 默认宽度和拉伸设置 |
height | object | 默认高度设置 |
关键: 创建特定于小部件的扩展文件后,您必须阅读 wix-cli-extension-registration 并按照“应用注册”部分更新 src/extensions.ts。
如果不完成步骤 2,站点小部件将无法在 Wix 编辑器中使用。
any,显式返回类型)@ts-ignore 注释每周安装
188
仓库
GitHub 星标
3
首次出现
2026年1月26日
安全审计
安装于
opencode166
cursor93
codex88
gemini-cli86
github-copilot82
kimi-cli77
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.
Follow these steps in order when creating a site widget:
src/extensions/site/widgets/custom-elements/<widget-name>/widget.tsx with React component and reactToWebComponent conversionpanel.tsx with WDS components and widget.getProp/setPropextensions.ts with extensions.customElement() and unique UUIDsrc/extensions.ts to import and use the new extensionSite widgets consist of two required files :
widget.tsx)React component converted to a web component using react-to-webcomponent:
panel.tsx)Settings panel shown in the Wix Editor sidebar:
@wix/editor widget APIwidget.getProp('kebab-case-name')widget.setProp('kebab-case-name', value)WixDesignSystemProvider > SidePanel > SidePanel.Contentinputs.selectColor() from @wix/editor with FillPreview — NOT <Input type="color">inputs.selectFont() from with a — NOT a text Inputimport React, { type FC, useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import reactToWebComponent from 'react-to-webcomponent';
interface WidgetProps {
title?: string;
targetDate?: string;
targetTime?: string;
bgColor?: string;
textColor?: string;
font?: string;
}
const CustomElement: FC<WidgetProps> = ({
title = 'Countdown',
targetDate = '',
targetTime = '00:00',
bgColor = '#ffffff',
textColor = '#333333',
font = "{}",
}) => {
const { font: textFont, textDecoration } = JSON.parse(font);
const [time, setTime] = useState({ days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: false });
useEffect(() => {
if (!targetDate) return;
const update = () => {
const target = new Date(`${targetDate}T${targetTime}`);
const diff = target.getTime() - Date.now();
if (diff <= 0) {
setTime({ days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: true });
return;
}
setTime({
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
hours: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
seconds: Math.floor((diff % (1000 * 60)) / 1000),
isExpired: false,
});
};
update();
const interval = setInterval(update, 1000);
return () => clearInterval(interval);
}, [targetDate, targetTime]);
const styles = {
wrapper: {
backgroundColor: bgColor,
padding: '24px 32px',
textAlign: 'center' as const,
display: 'inline-block',
},
title: {
font: textFont || '600 24px sans-serif',
color: textColor,
textDecoration,
marginBottom: '16px',
},
};
return (
<div style={styles.wrapper}>
{title && <div style={styles.title}>{title}</div>}
{/* Widget content */}
</div>
);
};
const customElement = reactToWebComponent(CustomElement, React, ReactDOM, {
props: {
title: 'string',
targetDate: 'string',
targetTime: 'string',
bgColor: 'string',
textColor: 'string',
font: 'string',
},
});
export default customElement;
Key Points:
targetDate, bgColor)reactToWebComponent config uses camelCase keys with 'string' typefont) from JSON strings: const { font: textFont, textDecoration } = JSON.parse(font)font CSS shorthand and textDecoration propertyimport React, { type FC, useState, useEffect, useCallback } from "react";
import { widget } from "@wix/editor";
import {
SidePanel,
WixDesignSystemProvider,
Input,
FormField,
TimeInput,
Box,
} from "@wix/design-system";
import "@wix/design-system/styles.global.css";
import { ColorPickerField } from "./components/ColorPickerField";
import { FontPickerField } from "./components/FontPickerField";
import { parseTimeValue } from "./utils";
const DEFAULT_BG_COLOR = "#0a0e27";
const DEFAULT_TEXT_COLOR = "#00ff88";
const DEFAULT_TEXT_FONT = "";
const DEFAULT_TEXT_DECORATION = "";
const Panel: FC = () => {
const [title, setTitle] = useState<string>("Countdown");
const [targetDate, setTargetDate] = useState<string>("");
const [targetTime, setTargetTime] = useState<string>("00:00");
const [bgColor, setBgColor] = useState<string>(DEFAULT_BG_COLOR);
const [textColor, setTextColor] = useState<string>(DEFAULT_TEXT_COLOR);
const [font, setFont] = useState({ font: DEFAULT_TEXT_FONT, textDecoration: DEFAULT_TEXT_DECORATION });
useEffect(() => {
Promise.all([
widget.getProp("title"),
widget.getProp("target-date"),
widget.getProp("target-time"),
widget.getProp("bg-color"),
widget.getProp("text-color"),
widget.getProp("font"),
])
.then(([titleVal, dateVal, timeVal, bgColorVal, textColorVal, fontString]) => {
setTitle(titleVal || "Countdown");
setTargetDate(dateVal || "");
setTargetTime(timeVal || "00:00");
setBgColor(bgColorVal || DEFAULT_BG_COLOR);
setTextColor(textColorVal || DEFAULT_TEXT_COLOR);
setFont(JSON.parse(fontString || "{}"));
})
.catch((error) => console.error("Failed to fetch widget properties:", error));
}, []);
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);
}, []);
const handleTimeChange = useCallback(({ date }: { date: Date }) => {
if (date) {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const newTime = `${hours}:${minutes}`;
setTargetTime(newTime);
widget.setProp("target-time", newTime);
}
}, []);
const handleBgColorChange = (value: string) => {
setBgColor(value);
widget.setProp("bg-color", value);
};
const handleTextColorChange = (value: string) => {
setTextColor(value);
widget.setProp("text-color", value);
};
const handleFontChange = (value: { font: string; textDecoration: string }) => {
setFont(value);
widget.setProp("font", JSON.stringify(value));
};
return (
<WixDesignSystemProvider>
<SidePanel width="300" height="100vh">
<SidePanel.Header title="Countdown Settings" />
<SidePanel.Content noPadding stretchVertically>
<Box direction="vertical" gap="24px">
<SidePanel.Field>
<FormField label="Title" required>
<Input
type="text"
value={title}
onChange={handleTitleChange}
placeholder="Enter countdown title"
/>
</FormField>
</SidePanel.Field>
<SidePanel.Field>
<FormField label="Target Date" required>
<Input
type="date"
value={targetDate}
onChange={handleDateChange}
/>
</FormField>
</SidePanel.Field>
<SidePanel.Field>
<FormField label="Target Time" required>
<TimeInput
value={parseTimeValue(targetTime)}
onChange={handleTimeChange}
/>
</FormField>
</SidePanel.Field>
<ColorPickerField
label="Background Color"
value={bgColor}
onChange={handleBgColorChange}
/>
<ColorPickerField
label="Text Color"
value={textColor}
onChange={handleTextColorChange}
/>
<FontPickerField
label="Text Font"
value={font}
onChange={handleFontChange}
/>
</Box>
</SidePanel.Content>
</SidePanel>
</WixDesignSystemProvider>
);
};
export default Panel;
Key Points:
widget.getProp() and widget.setProp() use kebab-case (e.g., "target-date", "bg-color")WixDesignSystemProvider > SidePanel > SidePanel.Content@wix/design-system (see references/SETTINGS_PANEL.md)@wix/design-system/styles.global.css for stylesColorPickerField with inputs.selectColor() from — NOT Critical: Props use different naming conventions in each file:
| File | Convention | Example |
|---|---|---|
widget.tsx (Props interface) | camelCase | targetDate, bgColor, textColor |
panel.tsx (widget API) | kebab-case | "target-date", "bg-color", "text-color" |
The web component automatically converts between camelCase (React props) and kebab-case (HTML attributes).
When using Wix Data API in widgets, you must handle the Wix Editor environment gracefully:
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:
{ window as wixWindow } from "@wix/site-window"await wixWindow.viewMode() before fetching dataviewMode === 'Editor', show a placeholder UI insteadFor color selection in settings panels, use ColorPickerField component with inputs.selectColor() from @wix/editor. Do NOT use <Input type="color">.
// components/ColorPickerField.tsx
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Box, FillPreview, SidePanel } from '@wix/design-system';
interface ColorPickerFieldProps {
label: string;
value: string;
onChange: (value: string) => void;
}
export const ColorPickerField: FC<ColorPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Box width="30px" height="30px">
<FillPreview
fill={value}
onClick={() => inputs.selectColor(value, { onChange: (val) => { if (val) onChange(val); } })}
/>
</Box>
</FormField>
</SidePanel.Field>
);
Usage in panel:
const handleBgColorChange = (value: string) => {
setBgColor(value);
widget.setProp("bg-color", value);
};
<ColorPickerField label="Background Color" value={bgColor} onChange={handleBgColorChange} />
Important: Use inputs.selectColor(value, { onChange }) from @wix/editor with FillPreview from WDS. This opens the native Wix color picker with theme colors, gradients, and more. Never use <Input type="color">.
For font selection in settings panels, use FontPickerField component with inputs.selectFont() from @wix/editor. Do NOT use a text Input.
// components/FontPickerField.tsx
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Button, Text, SidePanel } from '@wix/design-system';
interface FontValue {
font: string;
textDecoration: string;
}
interface FontPickerFieldProps {
label: string;
value: FontValue;
onChange: (value: FontValue) => void;
}
export const FontPickerField: FC<FontPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Button
size="small"
priority="secondary"
onClick={() => inputs.selectFont(value, { onChange: (val) => onChange({ font: val.font, textDecoration: val.textDecoration || "" }) })}
fullWidth
>
<Text size="small" ellipsis>Change Font</Text>
</Button>
</FormField>
</SidePanel.Field>
);
Usage in panel:
const [font, setFont] = useState<FontValue>({ font: "", textDecoration: "" });
const handleFontChange = (value: FontValue) => {
setFont(value);
widget.setProp("font", JSON.stringify(value));
};
<FontPickerField label="Text Font" value={font} onChange={handleFontChange} />
Important: Use inputs.selectFont(value, { onChange }) from @wix/editor with the callback pattern. This provides a rich font picker dialog with bold, italic, size, and typography features. Font values are stored as JSON strings.
src/extensions/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
Request: "Create a countdown timer widget"
Output:
Request: "Create a widget that displays products from a collection"
Output:
Request: "Create a calculator widget with customizable colors"
Output:
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.
Extension registration is MANDATORY and has TWO required steps.
Each site widget requires an extensions.ts file in its folder:
import { extensions } from "@wix/astro/builders";
export const sitewidgetMyWidget = extensions.customElement({
id: "{{GENERATE_UUID}}",
name: "My Widget",
tagName: "my-widget",
element: "./extensions/site/widgets/custom-elements/my-widget/widget.tsx",
settings: "./extensions/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".
| Property | Type | Description |
|---|---|---|
id | string | Unique static UUID v4 (generate fresh) |
name | string | Display name in editor |
tagName | string | HTML custom element tag (kebab-case) |
element | string | Path to widget React component |
settings | string | Path to settings panel component |
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.
any, explicit return types)@ts-ignore commentsWeekly Installs
188
Repository
GitHub Stars
3
First Seen
Jan 26, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode166
cursor93
codex88
gemini-cli86
github-copilot82
kimi-cli77
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
113,700 周安装
Dictionary API自动化教程:通过Rube MCP和Composio实现词典API操作自动化
1 周安装
detrack-automation:自动化追踪技能,集成Claude AI提升开发效率
1 周安装
Demio自动化工具包:通过Rube MCP和Composio实现Demio操作自动化
1 周安装
Deel自动化工具:通过Rube MCP与Composio实现HR与薪资操作自动化
1 周安装
Honeycomb (hc) 多智能体任务协调系统使用指南 | 开发者协作与项目管理工具
1 周安装
Liquid Glass 采用参考指南:iOS/macOS 界面材质迁移与无障碍优化
148 周安装
@wix/editorButton@wix/editor<Input type="color">FontPickerField with inputs.selectFont() from @wix/editor — NOT a text InputJSON.stringify() / JSON.parse()reactToWebComponent config | camelCase | targetDate: 'string' |
installation | object | Auto-add behavior |
width | object | Default width and stretch settings |
height | object | Default height settings |