OpenMobile Web SDK - React components for server-driven UI
npm install @openmobile/sdk-web




Native in-app experiences for React applications - update your UI instantly without app releases.
- ๐ฏ Native rendering - React components from JSON definitions
- ๐ Surveys - NPS, CSAT, and custom feedback forms
- ๐ Onboarding flows - Interactive user tours and walkthroughs
- ๐ข Feature announcements - In-app messages and changelogs
- ๐จ Visual editor - Design screens without coding
- โก Instant updates - No app store deployment required
- ๐ Type-safe - Full TypeScript support
- ๐งช Well-tested - Comprehensive test coverage
---
``bash`
npm install @openmobile/sdk-web
`bash`
yarn add @openmobile/sdk-web
`bash`
pnpm add @openmobile/sdk-web
Requirements:
- React 18.0+
- TypeScript 5.0+ (recommended)
---
In your app entry point (main.tsx or App.tsx):
`typescript
import { OpenMobileSDK } from '@openmobile/sdk-web';
// Initialize once at app startup
OpenMobileSDK.initialize({
apiKey: 'your-api-key-here',
baseUrl: 'https://api.openmobile.io' // optional
});
`
`typescript
import React from 'react';
import { OpenMobileScreen, EventListener } from '@openmobile/sdk-web';
const json =
{
"version": "1.0",
"id": "home_screen",
"root": {
"type": "column",
"properties": {
"id": "main",
"spacing": 16,
"padding": 20
},
"children": [
{
"type": "text",
"properties": {
"id": "title",
"text": "Welcome to OpenMobile!",
"fontSize": 24,
"fontWeight": "bold"
}
},
{
"type": "button",
"properties": {
"id": "cta",
"text": "Get Started"
},
"events": {
"onTap": {
"type": "navigate",
"route": "/getting-started"
}
}
}
]
}
};
function HomeScreen() {
return (
eventListener={new MyEventListener()}
/>
);
}
`
`typescript
import { EventListener } from '@openmobile/sdk-web';
import { useNavigate } from 'react-router-dom'; // or your routing library
class MyEventListener implements EventListener {
constructor(private navigate?: any) {}
onNavigate(route: string, params?: Record
console.log('Navigate to:', route, params);
// Use your routing library
this.navigate?.(route);
}
onOpenUrl(url: string, external: boolean) {
console.log('Open URL:', url, 'external:', external);
if (external) {
window.open(url, '_blank');
} else {
window.location.href = url;
}
}
onCustomEvent(action: string, payload?: Record
switch (action) {
case 'add_to_cart':
console.log('Add to cart:', payload?.product_id);
// Handle add to cart
break;
case 'share':
console.log('Share:', payload);
// Handle share
break;
}
}
onSubmitForm(endpoint: string, method: string, fields?: Record
console.log('Submit form to:', endpoint);
// Make API call
fetch(endpoint, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(fields)
});
}
onTrackAnalytics(eventName: string, properties?: Record
console.log('Track:', eventName, properties);
// Send to your analytics service (GA, Mixpanel, etc.)
}
}
// Usage in component
function MyComponent() {
const navigate = useNavigate();
const eventListener = new MyEventListener(navigate);
return
}
`
---
Vertical layout container (flex column).
`json`
{
"type": "column",
"properties": {
"id": "main_column",
"spacing": 16,
"padding": 20,
"scrollable": true,
"horizontalAlignment": "center",
"backgroundColor": "#F5F5F5"
},
"children": [...]
}
Display text content.
`json`
{
"type": "text",
"properties": {
"id": "title",
"text": "Hello World",
"fontSize": 24,
"fontWeight": "bold",
"color": "#000000",
"textAlign": "center",
"maxLines": 2
}
}
Interactive button.
`json`
{
"type": "button",
"properties": {
"id": "submit_btn",
"text": "Submit",
"variant": "primary",
"size": "large",
"fullWidth": true,
"backgroundColor": "#FF6B35",
"textColor": "#FFFFFF"
},
"events": {
"onTap": {
"type": "navigate",
"route": "/success"
}
}
}
Button Variants:
- primary: Filled button with background colorsecondary
- : Filled button with secondary coloroutline
- : Outlined button with transparent backgroundtext
- : Text-only button (no background, no border)
Button Sizes:
- small: Compact buttonmedium
- : Default sizelarge
- : Large button
Display images from URL.
`json`
{
"type": "image",
"properties": {
"id": "hero_image",
"url": "https://example.com/image.jpg",
"aspectRatio": "16:9",
"resizeMode": "cover",
"alt": "Hero banner",
"borderRadius": 12
}
}
Resize Modes:
- cover: Fills container, may crop imagecontain
- : Fits entirely, may have empty spacefill
- : Stretches to fill containercenter
- : Original size, centered
Container with elevation (shadow).
`json`
{
"type": "card",
"properties": {
"id": "promo_card",
"elevation": 4,
"cornerRadius": 16,
"padding": 20,
"clickable": true
},
"children": [...]
}
---
Navigate to another screen/route.
`json`
{
"type": "navigate",
"route": "/product/detail",
"params": {
"product_id": "PROD-123"
}
}
Open external URL or webview.
`json`
{
"type": "open_url",
"url": "https://example.com",
"external": true
}
Trigger custom actions in your app.
`json`
{
"type": "custom",
"action": "add_to_cart",
"payload": {
"product_id": "PROD-123",
"quantity": 1
}
}
Submit data to API.
`json`
{
"type": "submit_form",
"endpoint": "https://api.example.com/contact",
"method": "POST",
"fields": {
"name": "John Doe",
"email": "john@example.com"
}
}
Send analytics events.
`json`
{
"type": "track_analytics",
"eventName": "button_clicked",
"properties": {
"button_id": "cta_primary",
"screen": "home"
}
}
---
Supports hex colors and named colors:
`json`
"color": "#FF6B35"
"backgroundColor": "#FFFFFF"
"textColor": "blue"
Named colors: black, white, red, green, blue, yellow, orange, purple, pink, gray, brown, cyan, indigo, mint, teal
All spacing values in pixels:
`json`
"padding": 16,
"margin": 8,
"spacing": 12
`json`
"fontSize": 18,
"fontWeight": "bold",
"lineHeight": 1.5,
"textAlign": "center"
Font weights: thin (100), ultralight (200), light (300), regular (400), medium (500), semibold (600), bold (700), heavy (800), black (900)
---
`typescript
import React, { useState, useEffect } from 'react';
import { OpenMobileScreen, EventListener } from '@openmobile/sdk-web';
function ProductPage({ productId }: { productId: string }) {
const [json, setJson] = useState
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Fetch JSON from your API
fetch(https://api.example.com/products/${productId}/screen)
.then(res => res.json())
.then(data => {
setJson(JSON.stringify(data));
setIsLoading(false);
})
.catch(error => {
console.error('Error loading product:', error);
setIsLoading(false);
});
}, [productId]);
if (isLoading) return
return (
eventListener={new ProductEventListener()}
/>
);
}
class ProductEventListener implements EventListener {
onCustomEvent(action: string, payload?: Record
if (action === 'add_to_cart') {
const productId = payload?.product_id;
const price = payload?.price;
console.log(Adding product ${productId} ($${price}) to cart);`
// Add to cart logic
}
}
}
`typescript
import { OpenMobileScreen } from '@openmobile/sdk-web';
function MyScreen() {
return (
loadingComponent={
Loading your content...
{error}
$3
`typescript
import { Screen, Component, ComponentType } from '@openmobile/sdk-web';// Type-safe screen creation
const screen: Screen = {
version: '1.0',
id: 'my_screen',
root: {
type: ComponentType.COLUMN,
properties: {
id: 'root',
spacing: 16,
padding: 20,
},
children: [
{
type: ComponentType.TEXT,
properties: {
id: 'title',
text: 'Hello TypeScript!',
fontSize: 24,
},
},
],
},
};
// Use with OpenMobileScreen
`---
๐งช Testing
$3
`typescript
import { render, screen } from '@testing-library/react';
import { OpenMobileScreen } from '@openmobile/sdk-web';describe('OpenMobileScreen', () => {
it('renders text component', () => {
const json =
{; render( );
expect(screen.getByText('Hello Test')).toBeInTheDocument();
});
it('handles invalid JSON', () => {
const onError = jest.fn();
render( );
expect(onError).toHaveBeenCalled();
});
});
`---
๐ Documentation
- JSON Schema Spec
- Architecture Documentation
- Project Context
---
๐ Troubleshooting
$3
Solution: Call
OpenMobileSDK.initialize() before using any SDK features.$3
Solution: Validate your JSON against the schema. Check JSON_SCHEMA.md for correct format.
$3
Solution:
- Ensure URLs are HTTPS
- Check CORS headers on image server
- Verify image URLs are accessible
$3
Solution:
- Ensure
@openmobile/sdk-core is installed
- Check tsconfig.json has "moduleResolution": "node"
- Run npm install` to ensure all dependencies are installed---
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
MIT License - see LICENSE file for details.
- ๐ Documentation
- ๐ Report Issues
- ๐ฌ Discussions
- ๐ Changelog
---
Built with โค๏ธ by OpenMobile Team