SupHTML
The SupHTML object represents custom HTML content that can be displayed in Sup. It supports static content, images, videos, and interactive elements. It’s intended to be returned from the main() function, to be displayed in the chat.
{ "html": "<marquee>Hello World!</marquee>", "dimensions": { "width": 400, "height": 300 }, "type": "html",}Constructor
sup.html(html, options?)
const html = sup.html("<div>Hello World!</div>", { width: 800, height: 600,});Parameters:
html: The HTML content to display. Accepts:- A
stringof raw HTML - A
SupBundlefromsup.asset()(extracted zip with an entry point likeindex.html) - A
SupFilefromsup.asset()(a standalone.htmlfile uploaded as an asset — its content will be fetched automatically)
- A
options: Optional configuration object:{width: number; // Width in pixels (100-1600)height: number; // Height in pixels (100-1600)type?: "video" | "image" | "html"; // Content type to render asduration?: number; // Required for video typebannerUrl?: string; // URL for banner imagetailwind?: boolean | string; // Tailwind CSS enabled by default. Set false to disable, or string for specific versionwaitForSelector?: string; // CSS selector to wait for before capturing (for image/video types)webgl?: boolean; // Use WebGL-enabled renderer for image type (for Three.js, WebGL content)transparent?: boolean; // Render with transparent background for image typecallbacks?: string[]; // Function names that client JS can call via window.sup.exec()}
Properties
html
type: string
The HTML content to display.
dimensions
type: { width: number; height: number } | undefined
The dimensions for rendering the content. Both width and height must be between 100 and 1600 pixels.
type
type: "video" | "image" | "html" | undefined
The type to render the content as:
"html": Output interactive HTML content (default)"image": Output the HTML content as a static image"video": Output the HTML content as a video
duration
type: number | undefined
Required when type is “video”. Specifies the video duration in seconds.
bannerUrl
type: string | undefined
Optional URL for a banner image to display with the HTML content before launching.
tailwind
type: boolean | string
Tailwind CSS is enabled by default for HTML patches:
true(default): Includes the latest version of Tailwind CSS from the CDNstring: Includes a specific version of Tailwind CSS (e.g.,"4.0.0")false: Disables Tailwind CSS
When enabled, this automatically includes the Tailwind CSS library and Inter font, allowing you to use Tailwind utility classes in your HTML.
waitForSelector
type: string | undefined
For type: "image" or type: "video", this option allows you to specify a CSS selector to wait for before capturing the screenshot or starting video recording. This is useful when your HTML content loads dynamically or you need to wait for specific elements to appear before capturing.
// Wait for an element with class "chart" to appear before taking screenshotconst chart = sup.html(chartHtml, { width: 800, height: 600, type: "image", waitForSelector: ".chart"});webgl
type: boolean | undefined
For type: "image", enables a WebGL-capable renderer. Use this when your HTML content uses WebGL features like Three.js, WebGL canvases, or other GPU-accelerated graphics.
// Render Three.js or WebGL content as an imageconst render = sup.html(threeJsHtml, { width: 800, height: 600, type: "image", webgl: true});transparent
type: boolean | undefined
For type: "image", renders the HTML content with a transparent background instead of the default white background. The resulting image will be a PNG with alpha transparency.
// Render HTML as an image with transparent backgroundconst sticker = sup.html(` <div style="font-size: 64px;">🎉</div>`, { width: 200, height: 200, type: "image", transparent: true});callbacks
type: string[] | undefined
An array of function names that can be called from client-side JavaScript via window.sup.exec(). This enables two-way communication between the HTML content and the patch’s server-side code.
Callbacks work with both inline HTML strings and uploaded asset bundles, from both main() and launch().
Important: Functions listed in callbacks receive a SupUserEvent object as their parameter, NOT the raw arguments. Access the passed value via event.value.
// Inline HTML with callbacks (from main)function handleClick(event) { const city = event.value; return sup.ai.prompt(`Tell me about ${city}`);}
function main() { return sup.html(` <button onclick="ask()">Ask about Tokyo</button> <p id="result"></p> <script> async function ask() { const answer = await window.sup.exec("handleClick", "Tokyo"); document.getElementById("result").textContent = answer; } </script> `, { callbacks: ["handleClick"] });}// Asset bundle with callbacks (from launch)function generateResponse(event) { const userInput = event.value; return sup.ai.prompt(`Help with: ${userInput}`);}
function launch() { return sup.html(sup.asset("myApp"), { callbacks: ["generateResponse"] });}// Standalone HTML file asset (from main or launch)function main() { return sup.html(sup.asset("index.html"), { width: 800, height: 600 });}// Client-side JavaScript (in the HTML)// Call the server function and get the resultconst response = await window.sup.exec("generateResponse", "Hello!");// response is the return value directly (a string in this case)console.log(response); // "Hello! How can I help you today?"The window.sup.exec() function:
- First argument: function name (must be in
callbacksarray) - Remaining arguments: passed to the function via
event.value - Returns: a Promise that resolves to the function’s return value
Client-Side APIs
window.sup is automatically injected into all HTML content rendered by sup.html() — both inline HTML strings and uploaded asset bundles. You do not need to import, initialize, or set up any bridge. The following APIs are always available:
window.sup.exec()
Call server-side functions defined in callbacks. See above for details.
window.sup.share()
Share content directly to the chat from your HTML app.
// Share textawait window.sup.share('Hello world!');
// Share image URL (auto-detected)await window.sup.share('https://example.com/image.png');
// Share base64 data URL (MUST use explicit type!)const dataUrl = canvas.toDataURL('image/png');await window.sup.share({ url: dataUrl, type: 'image' });
// Share HTML contentawait window.sup.share({ html: '<b>Rich content</b>' });
// Share multiple itemsawait window.sup.share(['Text message', 'https://example.com/image.png']);Important: For base64 data URLs (like from canvas.toDataURL()), you MUST use the object format with explicit type: 'image'. Plain data URL strings will be treated as text, not images.
window.sup.user
Read-only object containing the current user’s information.
console.log(window.sup.user.id); // User IDconsole.log(window.sup.user.username); // Usernameconsole.log(window.sup.user.pfp); // Profile picture URLwindow.sup.chat
Read-only object containing the current chat’s information.
console.log(window.sup.chat.id); // Chat IDconsole.log(window.sup.chat.title); // Chat titleconsole.log(window.sup.chat.type); // "DIRECT" | "GROUP" | "CHANNEL" | "SOLOCHAT" | "THREAD" | "PAGE"Browser APIs & Permissions
HTML content rendered via sup.html() runs inside a sandboxed iframe (web) or WebView (mobile). The following browser APIs are available:
| API | Status | Notes |
|---|---|---|
| JavaScript | Allowed | Full JS execution |
| Microphone & Camera | Allowed | Via getUserMedia() — browser will prompt the user |
| Clipboard (write) | Allowed | Via navigator.clipboard.writeText() — browser may prompt |
| Geolocation | Allowed | Via navigator.geolocation — browser will prompt the user |
| Fullscreen API | Allowed | Via element.requestFullscreen() |
| Audio/Video playback | Allowed | Autoplay is enabled |
| WebGL / Canvas | Allowed | Full GPU-accelerated rendering |
| Web Audio API | Allowed | AudioContext, oscillators, etc. |
| Fetch / XHR | Allowed | But no cookies are sent (credentialless mode) |
| WebSockets | Allowed | Real-time connections work |
localStorage / sessionStorage | Unavailable | Runs in credentialless/incognito mode |
| Cookies | Unavailable | Credentialless mode prevents cookie access |
alert() / confirm() / prompt() | Blocked | Disabled on mobile, may work on web |
| Notifications | Blocked | Not permitted |
| Screen Capture | Blocked | getDisplayMedia() is not permitted |
| Bluetooth / USB / Serial | Blocked | Hardware APIs are not permitted |
| Top-level navigation | Blocked | Cannot navigate the parent page |
Microphone & Camera
Your HTML content can request microphone and camera access using the standard getUserMedia API. The browser will show its native permission prompt to the user.
// Request camera and microphoneconst stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true});
// Use the stream (e.g., attach to a video element)document.querySelector('video').srcObject = stream;Clipboard
You can write to the user’s clipboard:
await navigator.clipboard.writeText('Copied from a Sup patch!');Geolocation
You can request the user’s location. On mobile, the Sup app must have location permissions granted by the user.
navigator.geolocation.getCurrentPosition((position) => { console.log(position.coords.latitude, position.coords.longitude);});Usage Notes
// Static contentconst html = sup.html("<div>sup, world!</div>", { width: 400, height: 300,});
// As an imageconst image = sup.html("<div>sup, world!</div>", { width: 800, height: 600, type: "image",});
// As a videoconst video = sup.html("<marquee>sup, world!</marquee>", { width: 1280, height: 720, type: "video", duration: 10,});
// With bannerconst game = sup.html(gameHtml, { width: 300, height: 300, bannerUrl: "https://example.com/banner.png"});
// Tailwind CSS is enabled by default - just use utility classesconst styledContent = sup.html(` <div class="bg-blue-500 text-white p-4 rounded-lg"> <h1 class="text-xl font-bold">Hello Tailwind!</h1> </div>`, { width: 400, height: 200});
// Using specific Tailwind versionconst styledContentSpecific = sup.html(` <div class="bg-red-500 text-white p-8"> <p class="text-lg">Using Tailwind v4.0.0</p> </div>`, { width: 400, height: 200, tailwind: "4.0.0"});
// Screenshot with wait for selectorconst dynamicChart = sup.html(` <div> <div class="loading">Loading chart...</div> <div class="chart" style="display: none;">Chart content here</div> <script> setTimeout(() => { document.querySelector('.loading').style.display = 'none'; document.querySelector('.chart').style.display = 'block'; }, 1000); </script> </div>`, { width: 800, height: 600, type: "image", waitForSelector: ".chart"});JSX Support
In addition to using the sup.html() function, you can create HTML content using JSX syntax. JSX provides a more intuitive and component-based approach to building HTML content.
Basic JSX Syntax
function main() { return <sup.html width="400" height="300"> <h1>Hello, World!</h1> <p>This is JSX!</p> </sup.html>;}This is equivalent to:
function main() { return sup.html(` <h1>Hello, World!</h1> <p>This is JSX!</p> `, { width: 400, height: 300 });}JSX Props
You can pass all the same options as attributes to the <sup.html> element:
function main() { return <sup.html width="800" height="600" type="image" tailwind={true} > <div className="bg-blue-500 text-white p-4"> <h1 className="text-2xl">Styled with Tailwind</h1> </div> </sup.html>;}JavaScript Expressions in JSX
Use curly braces {} to embed JavaScript expressions:
function main() { const name = sup.user.username; const count = sup.get("count") || 0;
return <sup.html width="500" height="400"> <div> <h1>Welcome, {name}!</h1> <p>Count: {count}</p> <p>Time: {new Date().toLocaleTimeString()}</p> </div> </sup.html>;}Conditional Rendering
function main() { const isLoggedIn = sup.user.username !== "anonymous";
return <sup.html width="400" height="300"> {isLoggedIn && <h1>Welcome back, {sup.user.username}!</h1>} {!isLoggedIn && <h1>Please log in</h1>} </sup.html>;}JSX Components
Create reusable components as functions:
function main() { return <sup.html width="600" height="500"> <UserCard user={sup.user} /> <MessageList messages={sup.get("messages") || []} /> </sup.html>;}
function UserCard({ user }) { return ( <div className="user-card"> <h2>{user.username}</h2> <p>User ID: {user.id}</p> </div> );}
function MessageList({ messages }) { return ( <div className="messages"> {messages.map((message, index) => ( <div key={index} className="message"> <strong>{message.author}:</strong> {message.text} </div> ))} </div> );}Fragments
Use fragments <>...</> to group multiple elements without adding extra DOM nodes:
function main() { return <sup.html width="500" height="400"> <> <h1>Multiple Elements</h1> <p>First paragraph</p> <p>Second paragraph</p> </> </sup.html>;}Styling in JSX
You can include CSS styles directly in your JSX:
function main() { return <sup.html width="600" height="400"> <style>{` .container { padding: 20px; background: linear-gradient(45deg, #ff6b6b, #4ecdc4); font-family: Arial, sans-serif; } .title { color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); } `}</style> <div className="container"> <h1 className="title">Styled Content</h1> <p>Beautiful gradients and styling!</p> </div> </sup.html>;}Interactive Elements
Combine JSX with interactive features:
function main() { const count = sup.get("count") || 0;
return [ <sup.html width="400" height="200"> <div style={{ textAlign: "center", padding: "20px" }}> <h2>Counter: {count}</h2> <p>Click the button below to increment!</p> </div> </sup.html>, sup.button("Increment", handleIncrement) ];}
function handleIncrement() { const count = sup.get("count") || 0; sup.set("count", count + 1);}Mixed Content
You can combine JSX with other Sup elements:
function main() { return [ <sup.text>Here's some HTML content:</sup.text>, <sup.html width="500" height="300"> <div> <h1>Mixed Content Example</h1> <p>This HTML is created with JSX</p> </div> </sup.html>, sup.image("https://example.com/image.jpg") ];}Best Practices
- Use meaningful component names:
<UserProfile />instead of<Component1 /> - Keep components small and focused: Each component should have a single responsibility
- Use props for reusability: Pass data through props to make components flexible
- Organize your code: Put helper components at the bottom of your patch
- Use className instead of class: JSX uses
classNamefor CSS classes - Remember to close tags: JSX requires all tags to be properly closed (
<br />not<br>)