SandboxedFrame
StableA locked box for a mini app made by someone else, with clear limits and asks before it does anything.
Also known as iframe, sandbox, embed, artifact frame
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
A small page that asks to open a link
- Ask to open a link
What this can do. This page runs in a locked box and cannot see your account. It can ask to open a link, and it asks you every time.
<SandboxedFrame
source="Trip helper"
title="Trip budget"
srcDoc={html}
permissions={["open-links"]}
/>
// Inside the page:
parent.postMessage({ type: "tattva:resize", height: document.documentElement.scrollHeight }, "*");
parent.postMessage({ type: "tattva:open-link", url: "https://example.com/" }, "*");The page tells us how tall it is, so the frame fits it. Press its button and the frame asks before opening the link.
When to use it
A locked box for a small page the assistant wrote itself, outside our approved parts. The page cannot see the account, and anything it asks for needs a yes from the person each time.
Made of: Generated by label · List of what the page asked for · The locked frame · Permission question (Allow, Don't allow) · What this can do line
Use it for
- A mini app, like a budget calculator, that our approved parts cannot build.
- A page from another site that should only show things.
- A page that may ask to open a link or run a tool, with the person's approval.
Not for
- A screen our approved parts can draw: use GenUIRenderer.
- Asking for passwords or payment details.
Do and avoid
What this can do. This page runs in a locked box and cannot see your account. It can only show things on this screen.
What this can do. This page runs in a locked box and cannot see your account. It can only show things on this screen.
Do
- Use it only when our approved parts cannot do the job.
- Declare exactly what the page may ask for, and keep the list short.
- Keep the sandbox without allow-same-origin.
- Show who made the page and what it can do, next to the frame.
Avoid
- Don't build the page's HTML into our own page. Never use dangerouslySetInnerHTML for it.
- Don't let the page open links or take steps without asking, even for permissions it declared.
- Don't trust a message just because it says it is from the frame. Check who sent it and from where.
- Don't ask for passwords or payment details inside a generated page.
On a phone
- The frame fills the width, and the page inside decides its own layout, so build it for a narrow screen.
- The frame height follows the page up to the maximum, 640px by default, and scrolls inside after that.
- The permission buttons wrap under the question. They keep their size and have a 44px tap area on a touch screen.
Seen in products
- MCP Apps: the app runs in a sandboxed iframe, talks to the host only through messages, declares permissions and can be asked for consent before it calls a tool.
- MCP Apps: the host can turn off the app's ability to open links.
- Claude Artifacts: generated pages run in a sandboxed frame on a separate origin.
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| sourcerequired | string | none | Who made the page. Shown as 'Generated by <source>'. |
| titlerequired | string | none | Accessible name of the frame. |
| srcDoc | string | none | The page as a string. Its origin is opaque (event.origin is 'null'). Use this or src. |
| src | string | none | Address of a page. Messages are accepted only from that URL's origin. Use this or srcDoc. |
| permissions | ("open-links" | "call-tools")[] | [] | What the page may ask for. Shown to the person. Each request still needs a yes. |
| allowedTools | string[] | [] | Tool names the page may ask for. Other names are ignored. |
| onOpenLink | (url: string) => void | none | Called after the person agrees. Default opens a new tab with noopener and noreferrer. Only http and https links get this far. |
| onCallTool | (name: string, args: Record<string, unknown>) => unknown | Promise<unknown> | none | Called after the person agrees. The page gets a 'tattva:tool-result' message with ok true or false. |
| minHeight | number | 120 | Smallest height in px. Resize messages are clamped to this. |
| maxHeight | number | 640 | Largest height in px. Resize messages are clamped to this. |
| initialHeight | number | 240 | Height before the page sends a resize message. |
| allowForms | boolean | false | Adds allow-forms to the sandbox. |
| allowSameOrigin | boolean | false | Adds allow-same-origin to the sandbox. With scripts this lets the page remove its own sandbox. Leave off for pages you did not write. |
| summary | string | none | Replaces the plain-language 'What this can do' sentence. |
| labels | SandboxedFrameLabels | none | Overrides: generatedBy, whatItCanDo, allow, deny. |
| className | string | none | Extra classes on the wrapper. |
"Show code under the preview gives the code for the example on the stage."
In Figma
In Figma, one SandboxedFrame switches all of this from its properties panel, across 4 variants. Each property sets the React prop beside it, so design and code use the same names.
| Figma property | React | Values |
|---|---|---|
| Allow forms | Set in Figma only | On or off |
| Allow same origin | allowSameOrigin | On or off |
States
States this part has beyond its default look. Build each one your screen can reach. States set by a property are in the Code tab.
- permission request
- When the page asks to open a link or call a tool you allowed, a prompt with Allow and Don't allow appears below the frame.
- no permissions
- With no permissions, the note says the page can only show things on screen.
- resizing
- The frame grows or shrinks to the height the page reports, between minHeight and maxHeight.
- focus
- The frame shows a focus ring on keyboard focus.
Tokens
Change one of these and every part that uses it follows.
--surface--border--attention-soft--attention-fg--radius-card
Built in
- The frame has a name, so screen readers say what it holds.
- Who made it, what it asked for and what it can do are plain text next to the frame.
- A permission question moves focus to Allow, with Don't allow next to it.
- A clear outline shows when someone tabs into the frame.
- The frame grows to fit the page but stays between a smallest and a largest height.
What you need to do
- Give it a title that says what the page holds. Screen readers use it to name the frame.
- Check the page inside for accessibility yourself. The frame cannot fix it.
- Prefer GenUIRenderer when it can do the job, because its parts are already accessible.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide