Skip to content

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.

Example HTML
{
"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 string of raw HTML
    • A SupBundle from sup.asset() (extracted zip with an entry point like index.html)
    • A SupFile from sup.asset() (a standalone .html file uploaded as an asset — its content will be fetched automatically)
  • 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 as
    duration?: number; // Required for video type
    bannerUrl?: string; // URL for banner image
    tailwind?: boolean | string; // Tailwind CSS enabled by default. Set false to disable, or string for specific version
    waitForSelector?: 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 type
    callbacks?: 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 CDN
  • string: 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 screenshot
const 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 image
const 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 background
const 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 result
const 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 callbacks array)
  • 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 text
await 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 content
await window.sup.share({ html: '<b>Rich content</b>' });
// Share multiple items
await 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 ID
console.log(window.sup.user.username); // Username
console.log(window.sup.user.pfp); // Profile picture URL

window.sup.chat

Read-only object containing the current chat’s information.

console.log(window.sup.chat.id); // Chat ID
console.log(window.sup.chat.title); // Chat title
console.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:

APIStatusNotes
JavaScriptAllowedFull JS execution
Microphone & CameraAllowedVia getUserMedia() — browser will prompt the user
Clipboard (write)AllowedVia navigator.clipboard.writeText() — browser may prompt
GeolocationAllowedVia navigator.geolocation — browser will prompt the user
Fullscreen APIAllowedVia element.requestFullscreen()
Audio/Video playbackAllowedAutoplay is enabled
WebGL / CanvasAllowedFull GPU-accelerated rendering
Web Audio APIAllowedAudioContext, oscillators, etc.
Fetch / XHRAllowedBut no cookies are sent (credentialless mode)
WebSocketsAllowedReal-time connections work
localStorage / sessionStorageUnavailableRuns in credentialless/incognito mode
CookiesUnavailableCredentialless mode prevents cookie access
alert() / confirm() / prompt()BlockedDisabled on mobile, may work on web
NotificationsBlockedNot permitted
Screen CaptureBlockedgetDisplayMedia() is not permitted
Bluetooth / USB / SerialBlockedHardware APIs are not permitted
Top-level navigationBlockedCannot 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 microphone
const 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

Common Patterns
// Static content
const html = sup.html("<div>sup, world!</div>", {
width: 400,
height: 300,
});
// As an image
const image = sup.html("<div>sup, world!</div>", {
width: 800,
height: 600,
type: "image",
});
// As a video
const video = sup.html("<marquee>sup, world!</marquee>", {
width: 1280,
height: 720,
type: "video",
duration: 10,
});
// With banner
const game = sup.html(gameHtml, {
width: 300,
height: 300,
bannerUrl: "https://example.com/banner.png"
});
// Tailwind CSS is enabled by default - just use utility classes
const 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 version
const 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 selector
const 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

  1. Use meaningful component names: <UserProfile /> instead of <Component1 />
  2. Keep components small and focused: Each component should have a single responsibility
  3. Use props for reusability: Pass data through props to make components flexible
  4. Organize your code: Put helper components at the bottom of your patch
  5. Use className instead of class: JSX uses className for CSS classes
  6. Remember to close tags: JSX requires all tags to be properly closed (<br /> not <br>)