Tutorial: Building UI Components¶
Learn how to create interactive user interfaces with dropdowns, buttons, containers, segmented controls, accordions, dashboards, and more.
Overview¶
This tutorial covers:
- Input elements: dropdowns, text inputs, sliders
- Button callbacks for interactivity
- Container elements for progressive disclosure
- SegmentedControl for tabbed views
- Accordion for expand and collapse views
- Building dashboards with grid layouts
- Infographic elements for KPIs
Prerequisites¶
- Completed Your First App
- Completed Working with Data
Input Elements¶
Dropdown Selection¶
Use SingleDropdown for one selection or MultiDropdown for multiple:
from virtualitics_sdk import SingleDropdown, MultiDropdown
class FilterStep(Step):
def run(self, flow_metadata):
df = self._inLink.data.data
categories = sorted(df["category"].unique().tolist())
dropdown = SingleDropdown(
id="category_filter",
title="Select Category",
options=categories,
default=categories[0]
)
return Page(
title="Filter Data",
sections=[
Section(title="Filters", cards=[
Card(title="Category", content=[dropdown])
])
]
)
def action(self, flow_metadata):
selected = self.page.get_element_by_id("category_filter").value
df = self._inLink.data.data
filtered = df[df["category"] == selected]
return Page(...)
Text and Numeric Inputs¶
from virtualitics_sdk import TextInput, NumericSlider, NumericRange
# Free-text input
search = TextInput(
id="search_box",
title="Search",
placeholder="Enter a keyword..."
)
# Single value slider
threshold = NumericSlider(
id="threshold",
title="Confidence Threshold",
min_value=0.0,
max_value=1.0,
step=0.05,
default=0.5
)
# Range slider
price_range = NumericRange(
id="price_range",
title="Price Range",
min_value=0,
max_value=1000,
step=10,
default_min=100,
default_max=500
)
Date Range¶
from virtualitics_sdk import DateTimeRange
from datetime import datetime, timedelta
date_filter = DateTimeRange(
id="date_range",
title="Date Range",
default_start=datetime.now() - timedelta(days=30),
default_end=datetime.now()
)
Buttons and Callbacks¶
Buttons are the primary way to trigger actions. Each button takes an on_click callback.
Standard Event — Toast Notification¶
Returns a message string without re-rendering the page:
from virtualitics_sdk import Button, ButtonStyle, ButtonColor
from virtualitics_sdk.types.callbacks import standard_event_callback
@standard_event_callback
async def on_save(store_interface):
# perform some action
return "Changes saved successfully!"
save_btn = Button(
id="save_btn",
title="Save",
label="Save Changes",
style=ButtonStyle.PRIMARY,
color=ButtonColor.ACCENT,
show_confirmation=False,
on_click=on_save
)
Page Update — Modify Elements In Place¶
Modifies the current page and re-renders only changed elements:
from virtualitics_sdk.types.callbacks import page_update_callback
@page_update_callback
async def refresh_data(store_interface):
page = await store_interface.get_page()
table = page.get_element_by_id("data_table")
table.data = fetch_fresh_data()
refresh_btn = Button(
id="refresh_btn",
title="Refresh",
label="Refresh Data",
show_confirmation=False,
on_click=refresh_data
)
Drilldown — Open a Modal¶
Opens a modal or popover overlay with custom content:
from virtualitics_sdk.types.callbacks import drilldown_callback
from virtualitics_sdk.page.drilldown import DrilldownType, DrilldownSize
from virtualitics_sdk.store.drilldown_store_interface import DrilldownStoreInterface
@drilldown_callback(
drilldown_type=DrilldownType.FAST_MODAL,
drilldown_size=DrilldownSize.LARGE
)
async def show_details(card, input_data, store_interface: DrilldownStoreInterface):
card.add_content([
RichText("## Detail View"),
Table(data=detail_df)
])
details_btn = Button(
id="details_btn",
title="Details",
label="View Details",
show_confirmation=False,
on_click=show_details
)
Containers — Show/Hide Content¶
Container extends Card and can be toggled visible or hidden without a full page refresh:
from virtualitics_sdk import Container, Button
from virtualitics_sdk.types.callbacks import ContainerToggleCallback
# Create a hidden container
advanced_options = Container(
id="advanced_options",
title="Advanced Options",
visible=False
)
advanced_options.add_content([
NumericSlider(id="epochs", title="Epochs", min_value=1, max_value=100, default=10),
NumericSlider(id="lr", title="Learning Rate", min_value=0.001, max_value=1.0, step=0.001, default=0.01),
])
# Button to show it
show_btn = Button(
id="show_advanced",
title="Advanced",
label="Show Advanced Options",
show_confirmation=False,
on_click=ContainerToggleCallback(visible=True, container_id="advanced_options")
)
# Use both in a page
Section(
title="Configuration",
cards=[
Card(title="Settings", content=[
dropdown,
show_btn
]),
advanced_options
]
)
SegmentedControl — Tabbed Views¶
SegmentedControl lets users switch between different views within the same card:
from virtualitics_sdk import SegmentedControl
from virtualitics_sdk.page.card import Segment
# Create segments (each is a Card-like container)
overview_segment = Segment(label="Overview")
overview_segment.add_content([
Infographic(id="kpis", title="KPIs", data=[...]),
PlotlyPlot(id="trend", figure=trend_fig)
])
detail_segment = Segment(label="Details")
detail_segment.add_content([
Table(id="detail_table", data=detail_df)
])
raw_segment = Segment(label="Raw Data")
raw_segment.add_content([
Table(id="raw_table", data=raw_df)
])
# Combine into a segmented control
tabs = SegmentedControl(
id="view_tabs",
title="Data Views",
segments=[overview_segment, detail_segment, raw_segment],
active_segment_index=0
)
# Place in a card
Card(title="Analysis", content=[tabs])
Accordion — Expand and collapse views¶
Accordion lets users toggle the view of a card by expanding and collapsing:
from virtualitics_sdk import Accordion
info = Infographic(id="kpis", title="KPIs", data=[...])
plot = PlotlyPlot(id="trend", figure=trend_fig)
table1 = Table(id="detail_table", data=detail_df)
table2 = Table(id="raw_table", data=raw_df)
# Add them to the Accordion
accordion = Accordion(
id="accordion",
title="Data View",
content=[info, plot, table1, table2],
is_open=True,
)
# Place in a card
Card(title="Analysis", content=[accordion])
Infographic — KPI Metrics¶
Display key metrics prominently:
from virtualitics_sdk import (
Infographic, InfographData, InfographDataType, InfographicOrientation
)
metrics = Infographic(
id="dashboard_kpis",
title="Key Metrics",
data=[
InfographData(
label="Total Revenue",
value="$1.2M",
type=InfographDataType.CURRENCY
),
InfographData(
label="Growth",
value="+15%",
type=InfographDataType.PERCENTAGE
),
InfographData(
label="Active Users",
value="3,421",
type=InfographDataType.NUMBER
),
],
orientation=InfographicOrientation.HORIZONTAL
)
Building Dashboards¶
Use Dashboard, Row, and Column for grid-based layouts:
from virtualitics_sdk import Dashboard, Row, Column, DashboardOrientation
class DashboardStep(Step):
def run(self, flow_metadata):
df = self._inLink.data.data
# Create components
metrics = Infographic(id="kpis", title="KPIs", data=[...])
line_chart = PlotlyPlot(id="trend", figure=px.line(df, x="date", y="value"))
pie_chart = PlotlyPlot(id="breakdown", figure=px.pie(df, names="category", values="count"))
data_table = Table(id="table", data=df)
# Arrange in a grid
dashboard = Dashboard(
id="main_dashboard",
title="Analytics",
orientation=DashboardOrientation.VERTICAL,
content=[
Row(content=[metrics]), # Full-width metrics
Row(content=[ # Two charts side by side
Column(content=[line_chart]),
Column(content=[pie_chart])
]),
Row(content=[data_table]) # Full-width table
]
)
return Page(
title="Dashboard",
sections=[
Section(title="Overview", cards=[
Card(title="Dashboard", content=[dashboard])
])
]
)
Combining Input and Display¶
A common pattern: show inputs at the top, results below, and update results when inputs change.
class InteractiveStep(Step):
def run(self, flow_metadata):
df = self._inLink.data.data
regions = sorted(df["region"].unique().tolist())
return Page(
title="Explore",
sections=[
Section(title="Filters", cards=[
Card(title="Options", content=[
SingleDropdown(
id="region",
title="Region",
options=regions,
default=regions[0]
),
NumericSlider(
id="min_sales",
title="Minimum Sales",
min_value=0,
max_value=int(df["sales"].max()),
default=0
)
])
]),
Section(title="Results", cards=[
Card(title="Data", content=[Table(data=df)])
])
]
)
def action(self, flow_metadata):
df = self._inLink.data.data
region = self.page.get_element_by_id("region").value
min_sales = self.page.get_element_by_id("min_sales").value
filtered = df[(df["region"] == region) & (df["sales"] >= min_sales)]
fig = px.bar(filtered, x="product", y="sales", title=f"Sales in {region}")
regions = sorted(df["region"].unique().tolist())
return Page(
title=f"Region: {region}",
sections=[
Section(title="Filters", cards=[
Card(title="Options", content=[
SingleDropdown(id="region", title="Region",
options=regions, default=region),
NumericSlider(id="min_sales", title="Minimum Sales",
min_value=0, max_value=int(df["sales"].max()),
default=min_sales)
])
]),
Section(title=f"Results ({len(filtered)} rows)", cards=[
Card(title="Chart", content=[PlotlyPlot(figure=fig)]),
Card(title="Data", content=[Table(data=filtered)])
])
]
)
Next Steps¶
- Learn about LLM Integration
- Explore the Elements API Reference
- See the Callbacks Reference