Skip to content

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

Input Elements

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