<- Back to Layout Components

Definition

SurfaceHost reserves a region of the current layout for another named UI surface.

Scope

Use SurfaceHost when a module shell and its content are rendered as separate surfaces. The shell surface owns navigation, framing, and the host region. The content surface is rendered into the host by matching the host surface_id.

Do not use SurfaceHost as a generic visual container. Use Column, Row, ContentArea, ScrollArea, or FlexContainer when the children belong to the same surface.

id and surface_id identify different things. id is the component id of the host inside the shell surface. surface_id is the name of the separate content surface that should mount inside that host.

host = sdk.ui.SurfaceHost("module_content_host", surface_id="module_content")
builder.add(host)

The content route must use the same surface name:

builder.set_surface("module_content", shell_route="/module/shell")

The required match is SurfaceHost.surface_id == builder.surface_id. The host component id does not need to match the surface id.

Do not rely on SurfaceHost.children for fallback labels or placeholder content. The web client renders SurfaceHost as a mount point and displays the hosted surface when it exists; local placeholder content should live outside the host.

Properties

Property Type Default Notes
surface_id string required Named surface mounted by this host. Treat it as static for the host.
children list[str | Component] [] Inherited container field. Do not use it for cross-client placeholder content.
style string client default Standard style property.

Static Host

host = sdk.ui.SurfaceHost(
    "module_content_host",
    surface_id="module_content",
)
builder.add(host)

Module Shell

A navigation shell normally places SurfaceHost beside module navigation. Wrap the host in the layout containers that define scrolling, splitting, or sizing.

nav = sdk.ui.Column("module_nav", ["nav_overview", "nav_settings"])
builder.add(nav)

sidebar = sdk.ui.Sidebar("module_sidebar", [nav.id])
sidebar.set_property("width", 220)
sidebar.set_property("max_width", 220)
builder.add(sidebar)

host = sdk.ui.SurfaceHost("module_content_host", surface_id="module_content")
builder.add(host)

content_scroll = sdk.ui.ScrollArea("module_content_scroll", [host.id])
content_scroll.set_property("stretch", True)
content_scroll.set_property("transparent", True)
builder.add(content_scroll)

shell = sdk.ui.Row("module_shell", [sidebar.id, content_scroll.id])
shell.set_property("align", "fill")
shell.set_property("stretch", True)
builder.add(shell)

Content Surface

The content route must render into the same surface_id used by the host.

async def render(params: dict, session: dict):
    builder = sdk.ui.Builder()
    builder.set_surface("module_content", shell_route="/module/shell")

    builder.add(sdk.ui.Title("content_title", "Overview", level=2))
    builder.add(sdk.ui.Column("content_root", ["content_title"]))
    return builder

The SDK helper prepare_shell_surface(...) creates the same surface setup and adds an empty content container:

content_id = sdk.ui.prepare_shell_surface(
    builder,
    surface_id="module_content",
    shell_route="/module/shell",
    content_component_id="module_content_root",
)

Store Binding

Bind style when the host frame is driven by page or global store state.

builder.set_store("/components_test/surfacehost/style", "", scope="page")

host = sdk.ui.SurfaceHost("module_content_host", surface_id="module_content")
host.set_property(
    "style",
    bound.store("/components_test/surfacehost/style", scope="page", default=""),
)
builder.add(host)

Data Model Binding

Use data-model binding when the host style comes from the current surface data.

builder.set_data("/components_test/surfacehost_model/style", "")

host = sdk.ui.SurfaceHost("module_content_host", surface_id="module_content")
host.set_property(
    "style",
    bound.data("/components_test/surfacehost_model/style", default=""),
)
builder.add(host)

Property Updates

Declare capabilities before updating style directly. Do not update surface_id at runtime; create a host with the target surface id.

host = sdk.ui.SurfaceHost("module_content_host", surface_id="module_content")
host.allow("style.set")
builder.add(host)

Visibility And Permissions

show_if, hide_if, and required_permissions are available on SurfaceHost.

host.set_show_if({
    "conditions": [
        {"left": bound.store("/components_test/visibility/surfacehost_show", scope="page", default=True), "op": "==", "right": True}
    ]
})

host.set_hide_if({
    "conditions": [
        {"left": bound.store("/components_test/visibility/surfacehost_hide", scope="page", default=False), "op": "==", "right": True}
    ]
})

host.set_required_permissions(["components.layout.view"])

Notes

  • The host surface_id must match the content builder surface id.
  • Use one active host for a given content surface_id in a shell.
  • Keep navigation and content surface concerns separate.
  • Put scrolling or split behavior around SurfaceHost, not inside the content surface by default.
  • Use builder.set_surface(...) or prepare_shell_surface(...) for routes that render into the host.