Skip to content

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": {}
}
FieldDescription
versionManifest version (current: "2.0.0")
mainEntry point URL (usually empty "" for PandaBridge components)
propertiesConfiguration options shown in the property panel
eventsTriggers that can activate actions on other components
actionsOperations other components can call
resourcesMedia slots (images, videos, audio, zip files)
markersUser-defined data points
synchronizationGroups for cross-component state sync
queryableData exposed for data binding
systemAdvanced 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

TypeDescriptionUI Control
StringText valueText input
BooleanTrue/falseCheckbox
IntegerWhole numberNumber input
FloatDecimal numberNumber input
ColorColor valueColor picker
DropdownSelection from listDropdown menu
Image, Video, AudioMedia resourceFile picker
BindData binding expressionBinding editor
JSONStructured dataJSON editor
CodeCode with syntax highlightingCode editor
SelectionReference to another componentComponent picker

Property options

OptionTypeDescription
idstringUnique identifier (used in code)
namestringDisplay name in Studio
locale_nameobjectLocalized names (e.g., { "fr_FR": "Nom" })
typestringProperty type
valueanyDefault value
bindablebooleanAllow data binding
requiredbooleanMark as required
hiddenstring | booleanConditional visibility expression
placeholderstringPlaceholder text for inputs
separatorbooleanShow 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'"
}
]
}
{
"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

Terminal window
npm install pandasuite-bridge

Initialization

import PandaBridge from 'pandasuite-bridge';
PandaBridge.init(() => {
console.log('Bridge ready');
});

Receiving properties and data

// Called once when component loads
PandaBridge.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 change
PandaBridge.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 event
PandaBridge.send('onItemSelected', {
itemId: 'item-123',
itemName: 'Selected Item'
});
// Trigger a marker event
PandaBridge.send(PandaBridge.SYNCHRONIZE, [markerData, 'markerName']);

Listening for actions

// Listen for a specific action
PandaBridge.listen('setVolume', ([level]) => {
setVolume(level);
});
// Listen for all actions
PandaBridge.listen((action, args) => {
console.log('Action received:', action, args);
});

Synchronization

// Send sync data
PandaBridge.send(PandaBridge.SYNCHRONIZE, [currentValue, 'syncGroupId']);
// Receive sync data
PandaBridge.synchronize('syncGroupId', (value) => {
updateFromSync(value);
});

Markers (snapshots)

// Provide snapshot data when requested
PandaBridge.getSnapshotData(() => {
return {
timestamp: getCurrentTime(),
customData: getComponentState()
};
});
// Restore from snapshot data
PandaBridge.setSnapshotData(({ data }) => {
if (data) {
setCurrentTime(data.timestamp);
restoreState(data.customData);
}
});

Resources

// Resolve a resource path
const imagePath = PandaBridge.resolvePath('backgroundImage', '/default.png');
// Get full resource object
const 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 language
const 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

Terminal window
npm install pandasuite-bridge-react

Basic 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:

Local development

Terminal window
# Install dependencies
npm install
# Start development server
npm start

This starts a local server. In PandaSuite Studio:

  1. Add a Web component
  2. Enter your local URL (e.g., http://localhost:3000)
  3. Tap/Click Refresh Metadata to load your manifest

Building for production

Terminal window
# Create component zip
npm run build

This creates pandasuite-component.zip. Drag this file onto your PandaSuite project to add the component.

Publishing to GitHub

Terminal window
# Create a GitHub release
npm run release

This 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., onItemSelected rather than event1)
  • Provide localization: add locale_name for all user-facing strings to support French and English users
  • Handle Studio mode: use PandaBridge.isStudio to 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:

See also