Loading...
Loading...
Use when building interactive widgets, custom data displays, or configurable site components with settings panels. Triggers include widget, custom element, interactive component, editor component, configurable widget, web component.
npx skill4agent add wix/skills wix-cli-site-widgetsrc/site/widgets/custom-elements/<widget-name>/widget.tsxreactToWebComponentpanel.tsxwidget.getProp/setPropextensions.tsextensions.customElement()src/extensions.tswidget.tsxreact-to-webcomponentpanel.tsx@wix/editorwidget.getProp('kebab-case-name')widget.setProp('kebab-case-name', value)WixDesignSystemProvider > SidePanel > SidePanel.Contentimport 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;targetDatebgColorreactToWebComponent'string'fontimport 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"WixDesignSystemProvider > SidePanel > SidePanel.Content@wix/design-system@wix/design-system/styles.global.css| File | Convention | Example |
|---|---|---|
| camelCase | |
| kebab-case | |
| camelCase | |
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>
);
};{ window as wixWindow }"@wix/site-window"await wixWindow.viewMode()viewMode === 'Editor'FontPickerFieldinputs.selectFont()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}
/>
);
};inputs.selectFont()@wix/editorsrc/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.tsextensions.tsimport { 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,
},
});idrandomUUID(){{GENERATE_UUID}}"a1b2c3d4-e5f6-7890-abcd-ef1234567890"| Property | Type | Description |
|---|---|---|
| string | Unique static UUID v4 (generate fresh) |
| string | Display name in editor |
| string | HTML custom element tag (kebab-case) |
| string | Path to widget React component |
| string | Path to settings panel component |
| object | Auto-add behavior |
| object | Default width and stretch settings |
| object | Default height settings |
src/extensions.tsany@ts-ignore