Custom components
Custom components let you extend PandaSuite with your own interactive elements built using web technologies (HTML, CSS, JavaScript). Unlike native PandaSuite components, custom components run in a web view, giving you access to the entire web ecosystem: npm packages, React, Vue, or any JavaScript framework.
Using the PandaBridge libraries, your component can communicate with the rest of your PandaSuite project: receiving properties, triggering events, responding to actions, and synchronizing with other components.
If you only need to embed external web content without interacting with other PandaSuite components, use the Web component instead.
Use cases
Custom components are useful when you need to:
- Integrate third-party services: connect to APIs like payment processors, analytics platforms, or custom backends
- Add specialized functionality: build QR code scanners, signature pads, custom charts, or interactive visualizations
- Reuse existing code: package your JavaScript or React code as a PandaSuite component
- Extend native capabilities: access device features or browser APIs not available in standard components
What custom components can do
Custom components behave like native PandaSuite components. They can:
- Display configurable properties in the Studio property panel
- Emit events (triggers) that activate actions on other components
- Receive actions from other components
- Define markers (data points like timestamps or hotspots)
- Synchronize state with other components
- React to language changes in multilingual projects
- Receive dynamic data through data binding
Component manifest (pandasuite.json)
Every custom component includes a pandasuite.json file that describes its interface. This manifest defines what properties, events, actions, and other features appear in PandaSuite Studio.
Basic structure
{ "version": "2.0.0", "main": "", "properties": [], "events": [], "actions": [], "resources": [], "markers": [], "synchronization": [], "queryable": {}, "system": {}}| Field | Description |
|---|---|
version | Manifest version (current: "2.0.0") |
main | Entry point URL (usually empty "" for PandaBridge components) |
properties | Configuration options shown in the property panel |
events | Triggers that can activate actions on other components |
actions | Operations other components can call |
resources | Media slots (images, videos, audio, zip files) |
markers | User-defined data points |
synchronization | Groups for cross-component state sync |
queryable | Data exposed for data binding |
system | Advanced settings (localization, marker configuration) |
Properties
Properties appear in Studio’s property panel. Each property has a type that determines its input UI.
{ "properties": [ { "id": "url", "name": "URL", "locale_name": { "fr_FR": "URL" }, "type": "String", "value": "https://example.com", "bindable": true, "required": true }, { "id": "autoplay", "name": "Autoplay", "locale_name": { "fr_FR": "Lecture automatique" }, "type": "Boolean", "value": false } ]}Property types
| Type | Description | UI Control |
|---|---|---|
String | Text value | Text input |
Boolean | True/false | Checkbox |
Integer | Whole number | Number input |
Float | Decimal number | Number input |
Color | Color value | Color picker |
Dropdown | Selection from list | Dropdown menu |
Image, Video, Audio | Media resource | File picker |
Bind | Data binding expression | Binding editor |
JSON | Structured data | JSON editor |
Code | Code with syntax highlighting | Code editor |
Selection | Reference to another component | Component picker |
Property options
| Option | Type | Description |
|---|---|---|
id | string | Unique identifier (used in code) |
name | string | Display name in Studio |
locale_name | object | Localized names (e.g., { "fr_FR": "Nom" }) |
type | string | Property type |
value | any | Default value |
bindable | boolean | Allow data binding |
required | boolean | Mark as required |
hidden | string | boolean | Conditional visibility expression |
placeholder | string | Placeholder text for inputs |
separator | boolean | Show visual separator above |
Conditional visibility
Use the hidden property to show/hide properties based on other values:
{ "properties": [ { "id": "mode", "name": "Mode", "type": "Dropdown", "items": [ { "id": "simple", "name": "Simple" }, { "id": "advanced", "name": "Advanced" } ], "value": "simple" }, { "id": "advancedOption", "name": "Advanced Option", "type": "String", "hidden": "properties.mode.value != 'advanced'" } ]}Dropdown items
{ "id": "quality", "name": "Quality", "type": "Dropdown", "value": "medium", "items": [ { "id": "low", "name": "Low", "locale_name": { "fr_FR": "Basse" } }, { "id": "medium", "name": "Medium", "locale_name": { "fr_FR": "Moyenne" } }, { "id": "high", "name": "High", "locale_name": { "fr_FR": "Haute" } } ]}Numeric constraints
{ "id": "speed", "name": "Speed", "type": "Float", "value": 1.0, "restrict": { "min": 0.1, "max": 10.0, "step": 0.1 }}Events (triggers)
Events let your component trigger actions on other components. Define queryable data that becomes available when the event fires.
{ "events": [ { "id": "onItemSelected", "name": "Item selected", "locale_name": { "fr_FR": "Élément sélectionné" }, "queryable": { "itemId": "example-id", "__desc_itemId": { "name": "Item ID", "locale_name": { "fr_FR": "ID de l'élément" } }, "itemName": "Example Name", "__desc_itemName": { "name": "Item name", "locale_name": { "fr_FR": "Nom de l'élément" } } } } ]}The queryable object defines data exposed when the event fires. Use __desc_ prefixed keys to provide metadata for each field.
Actions
Actions are operations that other components can trigger on your component. Actions can have parameters.
{ "actions": [ { "id": "setVolume", "name": "Set volume", "locale_name": { "fr_FR": "Définir le volume" }, "params": [ { "id": "level", "name": "Volume level", "locale_name": { "fr_FR": "Niveau de volume" }, "type": "Float", "value": 1.0, "restrict": { "min": 0, "max": 1 } } ] }, { "id": "reset", "name": "Reset", "locale_name": { "fr_FR": "Réinitialiser" } } ]}Resources
Resources are media files users can attach to your component.
{ "resources": [ { "id": "backgroundImage", "name": "Background image", "locale_name": { "fr_FR": "Image de fond" }, "type": "Image", "bindable": true, "localization": true }, { "id": "dataArchive", "name": "Data archive", "type": "Zip", "required": true } ]}Resource types: Image, Video, Audio, Zip, JSON
Set localization: true to allow different resources per language.
Synchronization
Synchronization groups let components share state (like playback position or scroll position).
{ "synchronization": [ { "id": "playbackPosition", "name": "Synchronize playback", "locale_name": { "fr_FR": "Synchroniser la lecture" } } ]}Exposed data (queryable)
The queryable object exposes data that other components can access through data binding.
{ "queryable": { "progress": 0, "__desc_progress": { "name": "Progress", "locale_name": { "fr_FR": "Progression" }, "type": "number" }, "isPlaying": false, "__desc_isPlaying": { "name": "Is playing", "locale_name": { "fr_FR": "En lecture" } } }}System configuration
The system object controls advanced behaviors.
{ "system": { "localization": true, "markers": [ { "params": [ { "id": "timestamp", "name": "Time", "type": "Float" } ] } ], "actions": [ { "id": "toMarker", "name": "Go to marker" } ], "events": [ { "id": "triggerMarker", "name": "Marker reached" } ] }}Set "markers": false to disable the marker system entirely.
PandaBridge JavaScript API
The JavaScript library handles communication between your component and PandaSuite.
Installation
npm install pandasuite-bridgeInitialization
import PandaBridge from 'pandasuite-bridge';
PandaBridge.init(() => { console.log('Bridge ready');});Receiving properties and data
// Called once when component loadsPandaBridge.onLoad(({ properties, markers, resources }) => { console.log('Properties:', properties); console.log('Markers:', markers); console.log('Resources:', resources);
// Initialize your component with these values initComponent(properties);});
// Called when properties changePandaBridge.onUpdate(({ properties, markers, resources }) => { console.log('Updated properties:', properties);
// Update your component updateComponent(properties);});Sending events
Use PandaBridge.send() to emit events defined in your manifest:
// Trigger an eventPandaBridge.send('onItemSelected', { itemId: 'item-123', itemName: 'Selected Item'});
// Trigger a marker eventPandaBridge.send(PandaBridge.SYNCHRONIZE, [markerData, 'markerName']);Listening for actions
// Listen for a specific actionPandaBridge.listen('setVolume', ([level]) => { setVolume(level);});
// Listen for all actionsPandaBridge.listen((action, args) => { console.log('Action received:', action, args);});Synchronization
// Send sync dataPandaBridge.send(PandaBridge.SYNCHRONIZE, [currentValue, 'syncGroupId']);
// Receive sync dataPandaBridge.synchronize('syncGroupId', (value) => { updateFromSync(value);});Markers (snapshots)
// Provide snapshot data when requestedPandaBridge.getSnapshotData(() => { return { timestamp: getCurrentTime(), customData: getComponentState() };});
// Restore from snapshot dataPandaBridge.setSnapshotData(({ data }) => { if (data) { setCurrentTime(data.timestamp); restoreState(data.customData); }});Resources
// Resolve a resource pathconst imagePath = PandaBridge.resolvePath('backgroundImage', '/default.png');
// Get full resource objectconst resource = PandaBridge.resolveResource('backgroundImage');if (resource) { // Access responsive variants const largePath = resource.srcsets?.['1280w'] || resource.path;}Utility properties
// Check if running in Studio (vs. published app)if (PandaBridge.isStudio) { // Show placeholder or preview mode}
// Get current languageconst language = PandaBridge.currentLanguage;Complete example
import PandaBridge from 'pandasuite-bridge';
let state = { count: 0 };
PandaBridge.init(() => { // Load initial data PandaBridge.onLoad(({ properties }) => { state.count = properties.initialCount || 0; render(); });
// Handle updates PandaBridge.onUpdate(({ properties }) => { if (properties.initialCount !== undefined) { state.count = properties.initialCount; render(); } });
// Handle actions PandaBridge.listen('increment', () => { state.count++; render(); PandaBridge.send('onCountChanged', { count: state.count }); });
PandaBridge.listen('reset', () => { state.count = 0; render(); PandaBridge.send('onCountChanged', { count: state.count }); });
// Snapshot support PandaBridge.getSnapshotData(() => ({ count: state.count })); PandaBridge.setSnapshotData(({ data }) => { if (data) { state.count = data.count; render(); } });});
function render() { document.getElementById('counter').textContent = state.count;}PandaBridge React API
The React library provides hooks for easier integration with React components.
Installation
npm install pandasuite-bridge-reactBasic usage
import { PandaBridgeRoot, usePandaBridge } from 'pandasuite-bridge-react';
function MyComponent() { const { properties, setProperty, resources } = usePandaBridge({ actions: { increment: () => console.log('Increment called'), reset: () => console.log('Reset called') } });
return ( <div> <p>Count: {properties?.count || 0}</p> <button onClick={() => setProperty('count', (properties?.count || 0) + 1)}> Increment </button> </div> );}
export default function App() { return ( <PandaBridgeRoot> <MyComponent /> </PandaBridgeRoot> );}usePandaBridge hook
const { properties, // Current property values markers, // Marker data resources, // Resolved resources (by ID) setProperty, // Update a single property setProperties, // Update multiple properties addActions // Add action handlers} = usePandaBridge({ actions: { actionName: (args) => { /* handle action */ } }, synchronization: { syncGroupId: (value) => { /* handle sync */ } }, markers: { getSnapshotDataHook: () => ({ /* snapshot data */ }), setSnapshotDataHook: (data) => { /* restore from snapshot */ } }, component: { getScreenshotHook: (callback) => { /* custom screenshot */ }, onLanguageChanged: (lang) => { /* handle language change */ } }});Accessing resources
function ImageDisplay() { const { resources } = usePandaBridge();
const backgroundImage = resources?.backgroundImage;
if (!backgroundImage) { return <div>No image selected</div>; }
return <img src={backgroundImage.path} alt="Background" />;}Built-in UI components
The React library includes pre-styled components:
import { Button, Input, Dropdown, Checkbox, Alert, Tabs, Tab, Loader} from 'pandasuite-bridge-react';
function SettingsPanel() { return ( <div> <Input placeholder="Enter value" /> <Dropdown> <Dropdown.Item value="a">Option A</Dropdown.Item> <Dropdown.Item value="b">Option B</Dropdown.Item> </Dropdown> <Checkbox>Enable feature</Checkbox> <Button primary>Save</Button> <Alert type="info">Settings saved successfully</Alert> </div> );}Testing and publishing
Development setup
Start with a boilerplate project:
- JavaScript: js-boilerplate-component
- React: react-boilerplate-component
Local development
# Install dependenciesnpm install
# Start development servernpm startThis starts a local server. In PandaSuite Studio:
- Add a Web component
- Enter your local URL (e.g.,
http://localhost:3000) - Tap/Click Refresh Metadata to load your manifest
Building for production
# Create component zipnpm run buildThis creates pandasuite-component.zip. Drag this file onto your PandaSuite project to add the component.
Publishing to GitHub
# Create a GitHub releasenpm run releaseThis publishes your component as a GitHub release, making it available for others to use.
Best practices
- Start from a boilerplate: use the official JavaScript or React templates to ensure proper project structure
- Keep components focused: each component should do one thing well; split complex functionality into multiple components
- Use meaningful IDs: property, event, and action IDs should be descriptive and consistent (e.g.,
onItemSelectedrather thanevent1) - Provide localization: add
locale_namefor all user-facing strings to support French and English users - Handle Studio mode: use
PandaBridge.isStudioto show appropriate previews or placeholders when editing in Studio - Test across platforms: verify your component works on iOS, Android, and web exports
Limitations
- Custom components require web development skills (HTML, CSS, JavaScript)
- Components run inside a web view, which may limit access to some native device APIs
- Large component bundles can impact app loading performance
Example components
Explore existing custom components for reference:
- react-media-player-component: URL-based media player
- react-qr-reader-component: QR code scanner
- after-effects-component: Lottie animations
- More components on GitHub
See also
- Web component: embed web content without PandaBridge
- Data binding: connect data to component properties
- Interactive actions: trigger behaviors between components
- Triggers: events that activate actions