wesktop v0.11.1 /SDUI Primitive Reference
On this page

Complete reference for wesktop's 40 Server-Driven UI primitives: fields, types, defaults, output examples, and usage guidance organized by category.

#SDUI Primitive Reference

wesktop ships 40 Server-Driven UI primitives as Pydantic models in wesktop.sdui. Each model validates its props and serializes to the dict shape that the dashboard's SDUIRenderer expects: {"type": ..., "props": {...}} with an optional "if" key for conditional rendering.

All primitives inherit from _PrimitiveBase and expose a .to_node() method that produces the output dict. Every primitive also accepts an optional if_condition parameter (excluded from serialized props, lifted to a top-level "if" key on the node).

For untyped dict construction without validation, use the node() helper:

python
from wesktop.sdui import node

node("heading", content="Hello", level=2)
# {"type": "heading", "props": {"content": "Hello", "level": 2}}

#Prop Sub-Models

Several primitives use shared sub-models for structured data. These are not SDUI nodes themselves -- they describe items within a primitive's fields.

#TabItem

TabItem
FieldTypeDefaultDescription
labelstrrequiredDisplay text for the tab
valuestrrequiredProgrammatic identifier
BreadcrumbItem
FieldTypeDefaultDescription
labelstrrequiredDisplay text for the segment
hrefstr | NoneNoneNavigation target URL

#TimelineItem

TimelineItem
FieldTypeDefaultDescription
labelstrrequiredEvent description
timestr | NoneNoneTimestamp string
statusstr | NoneNoneEvent status
detailstr | NoneNoneAdditional detail text

#ColumnDef

Used by Table.

ColumnDef
FieldTypeDefaultDescription
keystrrequiredRow data key to display in this column
labelstr | NoneNoneColumn header text (falls back to key)
widthstr | NoneNoneCSS width value

#DataGridColumnDef

Used by DataGrid.

DataGridColumnDef
FieldTypeDefaultDescription
keystrrequiredRow data key
labelstrrequiredColumn header text
sortableboolTrueWhether the column supports sorting
filterableboolTrueWhether the column supports filtering
widthint | NoneNoneColumn width in pixels

#KVEntry

Used by KeyValue.

KVEntry
FieldTypeDefaultDescription
keystr | NoneNoneProgrammatic key
labelstr | NoneNoneDisplay label
valuestr | NoneNoneDisplay value

#OptionItem

Used by Select and Radio.

OptionItem
FieldTypeDefaultDescription
labelstrrequiredDisplay text
valuestrrequiredProgrammatic value

#Layout (9 primitives)

Structural containers that control how children are arranged.

#Stack

Flex container that arranges children in a column or row. The direction field determines the serialized node type ("column" or "row") and is excluded from props.

Stack
FieldTypeDefaultDescription
direction"column" | "row""column"Flex direction (becomes the node type)
gapint | NoneNoneGap between children in pixels
alignstr | NoneNoneCross-axis alignment
justifystr | NoneNoneMain-axis justification
wrapbool | NoneNoneWhether children wrap
python
Stack(direction="row", gap=8, align="center").to_node()
# {"type": "row", "props": {"gap": 8, "align": "center"}}

Stack(gap=16).to_node()
# {"type": "column", "props": {"gap": 16}}

Use Stack for any linear arrangement of children. Use direction="column" (default) for vertical stacking, direction="row" for horizontal.

#ZStack

Overlay container where children are positioned on the z-axis (position-absolute). Serializes as node type "stack".

ZStack
FieldTypeDefaultDescription
widthint | NoneNoneContainer width in pixels
heightint | NoneNoneContainer height in pixels
python
ZStack(width=200, height=200).to_node()
# {"type": "stack", "props": {"width": 200, "height": 200}}

Use ZStack when children need to overlap (badges on avatars, loading overlays on content).

#Spacer

Empty space between elements. Inserts a gap of optional fixed size.

Spacer
FieldTypeDefaultDescription
sizeint | NoneNoneFixed height in pixels
python
Spacer(size=24).to_node()
# {"type": "spacer", "props": {"size": 24}}

Spacer().to_node()
# {"type": "spacer", "props": {}}

Use Spacer for explicit whitespace that is not covered by a parent's gap property.

#Divider

Horizontal line separator. Has no configurable fields beyond if_condition.

python
Divider().to_node()
# {"type": "divider", "props": {}}

Use Divider to visually separate sections within a layout.

#Grid

CSS-grid layout container.

Grid
FieldTypeDefaultDescription
columnsint | str | NoneNoneNumber of columns (int) or CSS grid-template-columns value (str)
gapint | NoneNoneGap between grid cells in pixels
min_widthstr | NoneNoneMinimum column width for auto-fit layouts
python
Grid(columns=3, gap=16).to_node()
# {"type": "grid", "props": {"columns": 3, "gap": 16}}

Grid(columns="1fr 2fr 1fr", gap=8).to_node()
# {"type": "grid", "props": {"columns": "1fr 2fr 1fr", "gap": 8}}

Use Grid for two-dimensional layouts (dashboards, card grids, form layouts).

#Card

Elevated card container with optional title and subtitle.

Card
FieldTypeDefaultDescription
titlestr | NoneNoneCard title
subtitlestr | NoneNoneSubtitle text below the title
paddingint | str | NoneNoneInternal padding (pixels or CSS value)
elevatedbool | NoneNoneWhether the card has a shadow
python
Card(title="Deployment", subtitle="Production", elevated=True).to_node()
# {"type": "card", "props": {"title": "Deployment", "subtitle": "Production", "elevated": true}}

Use Card to group related content into a visually distinct container.

#Tabs

Tab switcher that selects among content panels.

Tabs
FieldTypeDefaultDescription
itemslist[TabItem][]Tab definitions
activestr | NoneNoneValue of the currently active tab
python
Tabs(items=[TabItem(label="Overview", value="overview"),
            TabItem(label="Logs", value="logs")],
     active="overview").to_node()
# {"type": "tabs", "props": {"items": [{"label": "Overview", "value": "overview"},
#                                       {"label": "Logs", "value": "logs"}],
#                              "active": "overview"}}

Use Tabs when the user needs to switch between distinct views within the same context.

Navigation breadcrumb trail showing the current location in a hierarchy.

Breadcrumb
FieldTypeDefaultDescription
itemslist[BreadcrumbItem][]Breadcrumb segments, in order from root to current
python
Breadcrumb(items=[BreadcrumbItem(label="Home", href="/"),
                  BreadcrumbItem(label="Settings")]).to_node()
# {"type": "breadcrumb", "props": {"items": [{"label": "Home", "href": "/"},
#                                              {"label": "Settings"}]}}

Use Breadcrumb for hierarchical navigation where the user needs to see and traverse the path to the current page.

#Empty

Empty-state placeholder shown when a section has no content.

Empty
FieldTypeDefaultDescription
messagestr | NoneNoneMessage to display
iconstr | NoneNoneIcon identifier
python
Empty(message="No deployments yet", icon="rocket").to_node()
# {"type": "empty", "props": {"message": "No deployments yet", "icon": "rocket"}}

Use Empty to provide a helpful message when a list, table, or section has no data.


#Display (10 primitives)

Read-only content rendering: text, headings, code, status indicators, and rich content.

#Heading

Section heading (h1 through h6).

Heading
FieldTypeDefaultDescription
contentstr""Heading text
levelint2Heading level (1-6, validated)
python
Heading(content="Server Status", level=1).to_node()
# {"type": "heading", "props": {"content": "Server Status", "level": 1}}

Use Heading for section titles and page structure.

#Text

Inline text span with optional styling.

Text
FieldTypeDefaultDescription
contentstr""Text content
size"xs" | "sm" | "md" | "lg" | "xl" | NoneNoneFont size
weight"normal" | "medium" | "semibold" | "bold" | NoneNoneFont weight
colorstr | NoneNoneText color
truncatebool | NoneNoneWhether to truncate with ellipsis
python
Text(content="Running", size="sm", weight="bold", color="green").to_node()
# {"type": "text", "props": {"content": "Running", "size": "sm",
#                              "weight": "bold", "color": "green"}}

Use Text for any inline text that needs styling beyond what Heading or Markdown provides.

#Code

Syntax-highlighted code block. Serializes as node type "code-block".

Code
FieldTypeDefaultDescription
contentstr""Code content
languagestr | NoneNoneLanguage for syntax highlighting
python
Code(content="print('hello')", language="python").to_node()
# {"type": "code-block", "props": {"content": "print('hello')", "language": "python"}}

Use Code for displaying source code, configuration files, or command output.

#Status

Status badge with semantic coloring.

Status
FieldTypeDefaultDescription
labelstr""Status text
variant"success" | "error" | "warning" | "info" | "neutral""neutral"Semantic color variant
python
Status(label="Healthy", variant="success").to_node()
# {"type": "status", "props": {"label": "Healthy", "variant": "success"}}

Use Status for indicating the state of services, deployments, or processes.

#Badge

Small rounded pill label or tag.

Badge
FieldTypeDefaultDescription
contentstr""Badge text
colorstr | NoneNoneBadge color
python
Badge(content="v2.1.0", color="blue").to_node()
# {"type": "badge", "props": {"content": "v2.1.0", "color": "blue"}}

Use Badge for labels, tags, version numbers, or counts.

#ProgressBar

Horizontal progress bar. Serializes as node type "progress-bar".

ProgressBar
FieldTypeDefaultDescription
valuefloat0Progress percentage (0-100, validated)
colorstr | NoneNoneBar color
labelstr | NoneNoneLabel text
python
ProgressBar(value=73.5, label="Upload", color="blue").to_node()
# {"type": "progress-bar", "props": {"value": 73.5, "label": "Upload", "color": "blue"}}

Use ProgressBar for showing completion of uploads, builds, or any bounded operation.

#Spinner

Loading spinner indicator.

Spinner
FieldTypeDefaultDescription
size"sm" | "md" | "lg""md"Spinner size
python
Spinner(size="lg").to_node()
# {"type": "spinner", "props": {"size": "lg"}}

Use Spinner for indicating that data is loading or an operation is in progress.

#Timeline

Vertical timeline of events.

Timeline
FieldTypeDefaultDescription
itemslist[TimelineItem][]Timeline entries
python
Timeline(items=[
    TimelineItem(label="Deployed", time="14:32", status="success"),
    TimelineItem(label="Tests passed", time="14:30"),
]).to_node()
# {"type": "timeline", "props": {"items": [
#     {"label": "Deployed", "time": "14:32", "status": "success"},
#     {"label": "Tests passed", "time": "14:30"}
# ]}}

Use Timeline for displaying a chronological sequence of events (deployment history, audit trail, build steps).

#Diff

Side-by-side or unified diff view.

Diff
FieldTypeDefaultDescription
old_textstr""Original text
new_textstr""Modified text
languagestr | NoneNoneLanguage for syntax highlighting
python
Diff(old_text="port = 8080", new_text="port = 9090", language="toml").to_node()
# {"type": "diff", "props": {"old_text": "port = 8080",
#                              "new_text": "port = 9090", "language": "toml"}}

Use Diff for code review, configuration change review, or any before/after comparison.

#Markdown

Rendered Markdown content.

Markdown
FieldTypeDefaultDescription
contentstr""Markdown source text
python
Markdown(content="## Notes\n\nDeploy to **production** after review.").to_node()
# {"type": "markdown", "props": {"content": "## Notes\n\nDeploy to **production** after review."}}

Use Markdown for rich formatted content where the source is already in Markdown format (README sections, documentation excerpts, user-authored notes).


#Data (6 primitives)

Structured data display: tables, grids, lists, key-value pairs, JSON trees, and hierarchical trees.

#Table

Data table with typed column definitions.

Table
FieldTypeDefaultDescription
columnslist[ColumnDef][]Column definitions
rows_keystr | NoneNoneState key that holds the row data array
python
Table(columns=[ColumnDef(key="name", label="Service"),
               ColumnDef(key="status", label="Status", width="100px")],
      rows_key="services").to_node()
# {"type": "table", "props": {"columns": [{"key": "name", "label": "Service"},
#                                           {"key": "status", "label": "Status",
#                                            "width": "100px"}],
#                               "rows_key": "services"}}

Use Table for displaying tabular data with defined column structure. Row data is provided via state using rows_key.

#DataGrid

Interactive data grid with sorting, filtering, and pagination. Serializes as node type "data-grid".

DataGrid
FieldTypeDefaultDescription
columnslist[DataGridColumnDef][]Column definitions with sort/filter config
datalist[dict][]Row data (inline, not state-driven)
page_sizeint25Rows per page
total_rowsint | NoneNoneTotal row count for server-side pagination
sortableboolTrueWhether sorting is enabled globally
filterableboolTrueWhether filtering is enabled globally
python
DataGrid(
    columns=[DataGridColumnDef(key="name", label="Name"),
             DataGridColumnDef(key="cpu", label="CPU %", sortable=True, filterable=False)],
    data=[{"name": "web-1", "cpu": 45.2}],
    page_size=50,
).to_node()
# {"type": "data-grid", "props": {"columns": [...], "data": [...],
#                                   "page_size": 50, "sortable": true, "filterable": true}}

Use DataGrid for large datasets that need interactive sorting, filtering, and pagination. Unlike Table, DataGrid carries its data inline rather than referencing state.

#List

Iterable list whose children are stamped per item.

List
FieldTypeDefaultDescription
items_keystr | NoneNoneState key that holds the items array
python
List(items_key="notifications").to_node()
# {"type": "list", "props": {"items_key": "notifications"}}

Use List for rendering a repeated template over an array from state (notifications, log entries, search results).

#KeyValue

Key-value display rendered as a definition list. Serializes as node type "key-value".

KeyValue
FieldTypeDefaultDescription
entrieslist[KVEntry][]Key-value entries
python
KeyValue(entries=[KVEntry(label="Host", value="prod-1.example.com"),
                  KVEntry(label="Uptime", value="14d 3h")]).to_node()
# {"type": "key-value", "props": {"entries": [{"label": "Host", "value": "prod-1.example.com"},
#                                               {"label": "Uptime", "value": "14d 3h"}]}}

Use KeyValue for displaying metadata, configuration summaries, or any labeled property list.

#JsonView

Interactive collapsible JSON tree viewer. Serializes as node type "json-view".

JsonView
FieldTypeDefaultDescription
data_keystr | NoneNoneState key that holds the JSON data
python
JsonView(data_key="response_body").to_node()
# {"type": "json-view", "props": {"data_key": "response_body"}}

Use JsonView for inspecting API responses, configuration objects, or any nested JSON structure.

#Tree

Hierarchical tree view with expandable nodes.

Tree
FieldTypeDefaultDescription
items_keystr | NoneNoneState key for the tree data
label_keystr | NoneNoneProperty name for node labels
children_keystr | NoneNoneProperty name for child arrays
python
Tree(items_key="filesystem", label_key="name", children_key="children").to_node()
# {"type": "tree", "props": {"items_key": "filesystem", "label_key": "name",
#                              "children_key": "children"}}

Use Tree for displaying hierarchical data (file systems, org charts, nested categories).


#Input (8 primitives)

Interactive controls that accept user input or dispatch commands.

#Button

Clickable button that dispatches a command.

Button
FieldTypeDefaultDescription
labelstr""Button text
variant"primary" | "danger" | "ghost" | "outline""primary"Visual style
commandstr | NoneNoneCommand to dispatch on click
confirmstr | NoneNoneConfirmation message shown before executing
disabledbool | str | NoneNoneDisabled state (bool) or state expression (str)
size"sm" | "md" | NoneNoneButton size
python
Button(label="Deploy", variant="primary", command="deploy",
       confirm="Deploy to production?").to_node()
# {"type": "button", "props": {"label": "Deploy", "variant": "primary",
#                                "command": "deploy",
#                                "confirm": "Deploy to production?"}}

Button(label="Delete", variant="danger", command="delete", disabled=True).to_node()
# {"type": "button", "props": {"label": "Delete", "variant": "danger",
#                                "command": "delete", "disabled": true}}

Use Button for any user-triggered action. Use confirm for destructive operations, disabled to prevent interaction based on state.

#Input

Single-line text input field.

Input
FieldTypeDefaultDescription
namestr""Form field name
labelstr | NoneNoneLabel text
placeholderstr | NoneNonePlaceholder text
input_type"text" | "number" | "email" | "password""text"HTML input type (serialized as "type")
python
Input(name="email", label="Email", placeholder="[email protected]",
      input_type="email").to_node()
# {"type": "input", "props": {"name": "email", "label": "Email",
#                               "placeholder": "[email protected]", "type": "email"}}

Use Input for single-line text entry (names, emails, numbers, passwords). The input_type field serializes as "type" in the output props via Pydantic alias.

#TextArea

Multi-line text input.

TextArea
FieldTypeDefaultDescription
namestr""Form field name
labelstr | NoneNoneLabel text
placeholderstr | NoneNonePlaceholder text
rowsint | NoneNoneNumber of visible text rows
python
TextArea(name="notes", label="Release Notes", rows=6).to_node()
# {"type": "textarea", "props": {"name": "notes", "label": "Release Notes", "rows": 6}}

Use TextArea for multi-line text entry (descriptions, notes, code snippets, comments).

#Select

Dropdown select control.

Select
FieldTypeDefaultDescription
namestr""Form field name
labelstr | NoneNoneLabel text
optionslist[OptionItem][]Available options
placeholderstr | NoneNonePlaceholder text when nothing is selected
python
Select(name="env", label="Environment",
       options=[OptionItem(label="Staging", value="staging"),
                OptionItem(label="Production", value="prod")],
       placeholder="Choose...").to_node()
# {"type": "select", "props": {"name": "env", "label": "Environment",
#                                "options": [{"label": "Staging", "value": "staging"},
#                                             {"label": "Production", "value": "prod"}],
#                                "placeholder": "Choose..."}}

Use Select for choosing one option from a predefined list.

#Checkbox

Boolean checkbox.

Checkbox
FieldTypeDefaultDescription
namestr""Form field name
labelstr | NoneNoneLabel text
checkedboolFalseInitial checked state
python
Checkbox(name="dry_run", label="Dry run", checked=True).to_node()
# {"type": "checkbox", "props": {"name": "dry_run", "label": "Dry run", "checked": true}}

Use Checkbox for boolean options in forms.

#Switch

Toggle switch (visually distinct from Checkbox but functionally similar).

Switch
FieldTypeDefaultDescription
namestr""Form field name
labelstr | NoneNoneLabel text
checkedboolFalseInitial toggle state
python
Switch(name="auto_deploy", label="Auto-deploy on merge").to_node()
# {"type": "switch", "props": {"name": "auto_deploy", "label": "Auto-deploy on merge",
#                                "checked": false}}

Use Switch for on/off settings where the visual emphasis on the binary state matters (feature flags, toggleable behaviors).

#Radio

Radio button group for selecting one option from a set.

Radio
FieldTypeDefaultDescription
namestr""Form field name (shared by all options in the group)
labelstr | NoneNoneGroup label text
optionslist[OptionItem][]Available options
python
Radio(name="region", label="Region",
      options=[OptionItem(label="US East", value="us-east-1"),
               OptionItem(label="EU West", value="eu-west-1")]).to_node()
# {"type": "radio", "props": {"name": "region", "label": "Region",
#                               "options": [{"label": "US East", "value": "us-east-1"},
#                                            {"label": "EU West", "value": "eu-west-1"}]}}

Use Radio when all options should be visible simultaneously and the user must pick exactly one.

#Slider

Numeric slider for selecting a value within a range.

Slider
FieldTypeDefaultDescription
namestr""Form field name
labelstr | NoneNoneLabel text
minfloat0Minimum value
maxfloat100Maximum value
stepfloat1Step increment
valuefloat | NoneNoneInitial value
python
Slider(name="replicas", label="Replicas", min=1, max=10, step=1, value=3).to_node()
# {"type": "slider", "props": {"name": "replicas", "label": "Replicas",
#                                "min": 1, "max": 10, "step": 1, "value": 3}}

Use Slider for numeric input where the valid range is bounded and continuous (replica counts, timeout values, thresholds).


#Feedback (3 primitives)

User-facing notifications, alerts, and log streams.

#Alert

Inline alert banner with semantic severity.

Alert
FieldTypeDefaultDescription
severity"info" | "success" | "warning" | "error""info"Alert severity level
titlestr | NoneNoneAlert title
messagestr""Alert body text
python
Alert(severity="error", title="Deploy Failed",
      message="Container health check timed out.").to_node()
# {"type": "alert", "props": {"severity": "error", "title": "Deploy Failed",
#                               "message": "Container health check timed out."}}

Use Alert for persistent in-page notifications that require user attention (errors, warnings, success confirmations).

#Toast

Ephemeral toast notification that auto-dismisses.

Toast
FieldTypeDefaultDescription
messagestr""Notification text
variant"info" | "success" | "warning" | "error""info"Semantic variant
duration_msint3000Auto-dismiss time in milliseconds
python
Toast(message="Settings saved", variant="success", duration_ms=2000).to_node()
# {"type": "toast", "props": {"message": "Settings saved", "variant": "success",
#                               "duration_ms": 2000}}

Use Toast for transient confirmations that do not need persistent visibility (save confirmations, clipboard copies, background task completions).

#Logs

Streaming log viewer with auto-scroll.

Logs
FieldTypeDefaultDescription
source_eventstr | NoneNoneSSE event name to stream from
max_linesint200Maximum number of lines to retain
auto_scrollboolTrueWhether to auto-scroll to the latest line
python
Logs(source_event="build-output", max_lines=500).to_node()
# {"type": "logs", "props": {"source_event": "build-output", "max_lines": 500,
#                              "auto_scroll": true}}

Use Logs for streaming real-time output (build logs, server logs, process output) via SSE.


#Overlay (4 primitives)

Modal dialogs, drawers, popovers, and confirmation prompts that layer above the main content.

Overlay modal dialog.

Modal
FieldTypeDefaultDescription
titlestr | NoneNoneDialog title
open_eventstr | NoneNoneEvent name that opens the modal
close_eventstr | NoneNoneEvent name that closes the modal
python
Modal(title="Create Service", open_event="show-create-modal",
      close_event="hide-create-modal").to_node()
# {"type": "modal", "props": {"title": "Create Service",
#                               "open_event": "show-create-modal",
#                               "close_event": "hide-create-modal"}}

Use Modal for focused interactions that require the user's full attention (forms, confirmations, detail views).

#Drawer

Slide-in side panel.

Drawer
FieldTypeDefaultDescription
titlestr | NoneNonePanel title
position"left" | "right""right"Which side the drawer slides in from
widthstr | NoneNonePanel width (CSS value)
python
Drawer(title="Settings", position="right", width="400px").to_node()
# {"type": "drawer", "props": {"title": "Settings", "position": "right", "width": "400px"}}

Use Drawer for secondary content or settings that the user can access without leaving the current page.

#Popover

Popover tooltip or flyout anchored to a trigger element.

Popover
FieldTypeDefaultDescription
triggerstr | NoneNoneElement identifier that triggers the popover
placement"top" | "bottom" | "left" | "right""bottom"Position relative to the trigger
python
Popover(trigger="info-icon", placement="top").to_node()
# {"type": "popover", "props": {"trigger": "info-icon", "placement": "top"}}

Use Popover for contextual help, tooltips, or small menus attached to a specific element.

#Confirm

Confirmation dialog for guarding destructive actions.

Confirm
FieldTypeDefaultDescription
titlestr | NoneNoneDialog title
messagestr""Confirmation question
confirm_labelstr"Confirm"Text on the confirm button
cancel_labelstr"Cancel"Text on the cancel button
actionstr | NoneNoneCommand dispatched on confirmation
python
Confirm(title="Delete Service",
        message="This will permanently delete the service and all its data.",
        confirm_label="Delete", cancel_label="Keep",
        action="delete-service").to_node()
# {"type": "confirm", "props": {"title": "Delete Service",
#                                 "message": "This will permanently delete...",
#                                 "confirm_label": "Delete", "cancel_label": "Keep",
#                                 "action": "delete-service"}}

Use Confirm when an action is destructive or irreversible and the user should explicitly approve before proceeding.


#Conditional Rendering

All primitives accept an optional if_condition parameter. When set, the serialized node includes an "if" key at the top level (not inside props). The renderer evaluates the condition against the current state to decide whether to render the node.

python
Button(label="Rollback", command="rollback",
       if_condition="deployment.status == 'failed'").to_node()
# {"type": "button",
#  "props": {"label": "Rollback", "variant": "primary", "command": "rollback"},
#  "if": "deployment.status == 'failed'"}

#Provider Registry

SDUI providers are async callables that return (ui_tree, initial_state). Three functions manage the registry:

  • register_sdui_provider(name, provider) -- register a provider by name
  • get_sdui_provider(name) -- look up a provider (returns None if not found)
  • list_sdui_providers() -- list all registered provider names
python
from wesktop.sdui import register_sdui_provider, node

async def dashboard_provider():
    ui_tree = node("column", [
        node("heading", content="Dashboard", level=1),
        node("text", content="Welcome"),
    ], gap=16)
    initial_state = {"user": "admin"}
    return ui_tree, initial_state

register_sdui_provider("dashboard", dashboard_provider)
Search