Prefab UI
Prefab is a Python DSL for building interactive React UIs. You compose
components using context managers, wire up actions as Pydantic models, and
wrap everything in a PrefabApp.
This skill covers patterns and layout. For component-specific API details
(props, variants, examples), use the component search tool with
detail=True.
Core Pattern
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column, Heading, Input, Button, Badge
from prefab_ui.components.control_flow import If
from prefab_ui.actions import ShowToast
from prefab_ui.actions.mcp import CallTool
with PrefabApp(state={"query": "", "results": []}) as app:
with Column(gap=4):
Heading("User Search")
Input(name="query", placeholder="Search...")
Button(
"Search",
"search_users",
arguments={"q": "{{ query }}"},
result_key="results",
$error }}", variant="error"),
),
)
with If("results.length > 0"):
Badge("{{ results.length }} found", variant="success")
PrefabAppis the outermost context manager — children auto-append- Container components (
Column,Card, etc.) usewithblocks state=takes a plain dict of initial client-side state{{ key }}templates resolve against state at render time- Actions like
CallToolcall server tools;result_keywrites results into state
Always pass state as a plain dict: PrefabApp(state={"count": 0}).
Imports
from prefab_ui.app import PrefabApp
# Components — import what you need
from prefab_ui.components import (
# Layout
Column, Row, Grid, GridItem, Container,
Dashboard, DashboardItem, Div, Span,
# Typography
Heading, Text, H1, H2, H3, H4, P, Lead, Large, Small, Muted,
BlockQuote, Label, Link, Markdown, Code,
# Cards
Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter,
# Data display
DataTable, DataTableColumn, Metric, Badge, Dot,
Ring, Progress, Separator, Loader, Icon,
Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
# Form controls
Form, Field, Input, Textarea, Button, ButtonGroup,
Select, SelectOption, Checkbox, Switch, Slider,
Radio, RadioGroup, DatePicker, Calendar,
Combobox, ComboboxOption, ChoiceCard,
# Interactive
Tabs, Tab, Accordion, AccordionItem,
Dialog, Popover, Tooltip, HoverCard, Pages, Page,
# Media
Image, Audio, Video, Embed, Svg, DropZone,
)
from prefab_ui.components.charts import (
BarChart, LineChart, AreaChart, PieChart,
RadarChart, RadialChart, ScatterChart, Sparkline,
ChartSeries,
)
from prefab_ui.components.control_flow import ForEach, If, Elif, Else
# Actions
from prefab_ui.actions import SetState, ToggleState, ShowToast, OpenLink
from prefab_ui.actions.mcp import CallTool, SendMessage, UpdateContext
Never import from prefab_ui directly except PrefabApp via
prefab_ui.app. Spinner does not exist — use Loader. ChartSeries
is in prefab_ui.components.charts, not prefab_ui.components.
Layout
Grid vs Row
Use Grid for card layouts and anything that needs equal or proportional
columns. Use Row for inline elements (badges, icon + text, buttons):
# Equal-width cards — Grid handles sizing automatically
with Grid(columns=3, gap=4):
with Card(css_class="p-6"):
Metric(label="Revenue", value="$1.2M")
with Card(css_class="p-6"):
Metric(label="Users", value="4,821")
with Card(css_class="p-6"):
Metric(label="Uptime", value="99.9%")
# Proportional widths (2:1 ratio)
with Grid(columns=[2, 1], gap=4):
with Card():
with CardContent():
LineChart(...)
with Card():
with CardContent():
Text("Summary")
# Row for inline elements only
with Row(gap=2, align="center"):
Icon("circle-check")
Text("Completed")
Badge("3 items")
Card Patterns
Two patterns — don't mix them in a single Card:
# Structured: sub-components have built-in padding
with Card():
with CardHeader():
CardTitle("Settings")
CardDescription("Manage your preferences")
with CardContent():
Input(name="name")
with CardFooter():
Button("Save")
# Simple: direct children, add padding yourself
with Card(css_class="p-6"):
Metric(label="Total", value="{{ total }}")
Tabs
Content goes inside each Tab — Tab is a container, not just a header:
with Tabs(default_value="overview"):
with Tab("Overview", value="overview"):
with Card():
with CardContent():
Text("overview content")
with Tab("Settings", value="settings"):
Text("settings content")
Metric
Metric(label="Revenue", value="$1.2M", delta="+12%", trend="up",
trend_sentiment="positive", description="vs last month")
delta= is the change text. trend= is "up"/"down"/"neutral" (sets the
arrow icon). trend_sentiment= is "positive"/"negative"/"neutral" (sets
the color).
DataTable
rows= (not data=), columns use key= and header= (not label=):
DataTable(
columns=[
DataTableColumn(key="name", header="Name", sortable=True),
DataTableColumn(key="email", header="Email"),
],
rows=users, # list of dicts, or "{{ state_key }}"
search=True,
search_placeholder="Search contacts...",
paginated=True,
)
Use search_placeholder= to customize the search input text.
on_row_click= actions fire when the row is clicked and receive the clicked row
as $event. Access row fields through $event, for example
{{ $event.email }} or EVENT.email.
Layout Props
Column, Row, Grid accept gap, align, justify as native props:
Column(gap=4) # gap-4
Row(gap=2, justify="between") # gap-2 justify-between
Grid(columns=3, gap=4) # grid-cols-3 gap-4
Use css_class for anything beyond these: Column(css_class="max-w-2xl mx-auto").
For app-level styling, use PrefabApp(css=[...]) for inline CSS strings and
PrefabApp(stylesheets=[...]) only for external CSS URLs. Stylesheet URLs are
loaded as <link> tags and may affect CSP; inline CSS is loaded as <style>.
Carousel
Carousel cycles through children. Each direct child becomes one slide. Three modes through configuration:
# Interactive carousel — arrows, dots, swipe
with Carousel(show_dots=True):
Card(...)
Card(...)
# Peek — show partial adjacent slides (1.3 = 1 full + 30% of next)
with Carousel(visible=1.3, align="center", dim_inactive=True):
Card(...)
# Multiple visible slides
with Carousel(visible=3, gap=16):
Card(...)
# Auto-advancing reel
with Carousel(auto_advance=3000, show_controls=False):
Card(...)
# Vertical reel
with Carousel(direction="down", visible=3, gap=16, auto_advance=2000, show_controls=False):
Div(style={"height": "160px"})
# Marquee — continuous smooth scroll
with Carousel(continuous=True, speed=3, show_controls=False):
Badge("A")
Badge("B")
# Fade transition
with Carousel(effect="fade", auto_advance=2000, show_controls=False):
Card(...)
Key props: visible (int/float/None), gap, direction (left/right/up/down),
auto_advance (ms), continuous, speed (1-10), effect (slide/fade),
dim_inactive, show_controls, controls_position (overlay/outside/gutter),
show_dots, align (start/center/end), loop.
Actions
Actions are Pydantic models. All support on_success and on_error:
# Client-side (instant)
SetState("count", "{{ count + 1 }}")
ToggleState("showAdvanced")
ShowToast("Done!", variant="success")
OpenLink("https://example.com")
# MCP round-trip
CallTool("search", arguments={"q": "{{ query }}"}, result_key="results")
SendMessage("Summarize {{ item }}")
UpdateContext(content="User selected {{ item }}")
# Chaining — | composes actions into a sequential list
Button(
"Save",
True)
| CallTool("save", result_key="saved")
| SetState("loading", False),
)
# Explicit lists remain supported
Button("Reset", 0), ShowToast("Reset")])
on_mount
Every component (and PrefabApp) supports on_mount — actions that fire
when the component mounts. Useful for initializing data, starting polling,
or running setup logic:
# Poll a server tool every 3 seconds when the app loads
PrefabApp(
view=dashboard_view,
duration=3000,
RESULT)),
),
)
# Load data when a conditional section becomes visible
with Condition("{{ showDetails }}"):
Column(
RESULT)),
children=[Text("{{ details }}")],
)
Expressions
String props support {{ expression }} templates:
Text("Hello, {{ user.name }}!")
Text("{{ items.length }} items ({{ total | currency }})")
Button("{{ loading ? 'Loading...' : 'Submit' }}")
Supports dot paths, arithmetic (+ - * /), comparisons (== !=
> <), logical (&& || !), ternary (? :), and pipes
(| upper, | currency, | length, | truncate:50, etc.).
Special variables: $event (triggering value in onChange/onClick),
$error (error message in onError), $index and $item (in ForEach).
Naming Convention
Python uses snake_case; wire protocol uses camelCase. Mapping is
automatic: on_click → onClick, css_class → cssClass, etc.
Positional Arguments
Most components accept their primary field positionally:
Button("Click me") # label
Text("Hello") # content
Heading("Title", level=2) # content, level
Tab("General") # title
ForEach("users") # key
CallTool("search") # tool
SetState("count", 42) # key, value
ShowToast("Saved!") # message