Tutorial 2: UI Components
The tango-api package provides themed components that match Tango’s look and feel. This tutorial walks through each one with code examples.
Starting from the Hello World instrument, we’ll enhance the sidebar panel to showcase the component library.
Layout components
Section titled “Layout components”UISection
Section titled “UISection”Groups related content with an optional title and description:
<UISection title="Settings" description="Configure your instrument"> {/* section content */}</UISection>UICard
Section titled “UICard”A bordered container for content blocks:
<UICard> <p>Card content goes here</p></UICard>UITabs
Section titled “UITabs”Tab navigation with content panels:
<UITabs tabs={[ { value: "overview", label: "Overview", content: <p>Overview content</p> }, { value: "details", label: "Details", content: <p>Detail content</p> }, ]} initialValue="overview"/>Input components
Section titled “Input components”UIButton
Section titled “UIButton”Triggers actions. Uses a label prop for the button text:
<UIButton label="Click me" variant="primary" onClick={() => console.log("clicked")} /><UIButton label="Secondary" variant="secondary" /><UIButton label="Danger" variant="danger" /><UIButton label="Ghost" variant="ghost" size="sm" />Variants: primary, secondary, danger, ghost
Sizes: sm, md
UIInput
Section titled “UIInput”Single-line text input:
const [name, setName] = useState("");
<UIInput value={name} placeholder="Enter your name" onInput={setName}/>UITextarea
Section titled “UITextarea”Multi-line text input:
const [notes, setNotes] = useState("");
<UITextarea value={notes} placeholder="Write notes..." rows={6} onInput={setNotes}/>UISelect
Section titled “UISelect”Dropdown selection:
const [color, setColor] = useState("red");
<UISelect options={[ { value: "red", label: "Red" }, { value: "blue", label: "Blue" }, { value: "green", label: "Green" }, ]} value={color} onChange={setColor}/>UIToggle
Section titled “UIToggle”On/off switch with label:
const [enabled, setEnabled] = useState(false);
<UIToggle label="Enable feature" checked={enabled} onChange={setEnabled}/>UICheckbox
Section titled “UICheckbox”Checkbox with label:
const [agreed, setAgreed] = useState(false);
<UICheckbox label="I agree to the terms" checked={agreed} onChange={setAgreed}/>UIRadioGroup
Section titled “UIRadioGroup”Radio button group:
const [size, setSize] = useState("medium");
<UIRadioGroup name="size" options={[ { value: "small", label: "Small" }, { value: "medium", label: "Medium" }, { value: "large", label: "Large" }, ]} value={size} onChange={setSize}/>UISegmentedControl
Section titled “UISegmentedControl”Segmented button strip (like iOS):
const [view, setView] = useState("list");
<UISegmentedControl options={[ { value: "list", label: "List" }, { value: "grid", label: "Grid" }, ]} value={view} onChange={setView}/>Display components
Section titled “Display components”UIBadge
Section titled “UIBadge”Colored label for status or metadata:
<UIBadge label="Active" tone="success" /><UIBadge label="Warning" tone="warning" /><UIBadge label="Error" tone="danger" /><UIBadge label="Info" tone="info" /><UIBadge label="Default" tone="neutral" />Tones: neutral, info, success, warning, danger
UIEmptyState
Section titled “UIEmptyState”Placeholder for empty views:
<UIEmptyState title="No items yet" description="Create your first item to get started." action={<UIButton label="Create" variant="primary" onClick={handleCreate} />}/>List components
Section titled “List components”UIList / UIListItem
Section titled “UIList / UIListItem”Simple list with optional selection:
const [selected, setSelected] = useState<string | null>(null);
<UIList> {items.map((item) => ( <UIListItem key={item.id} title={item.name} subtitle={item.description} active={selected === item.id} onClick={() => setSelected(item.id)} /> ))}</UIList>UISelectionList
Section titled “UISelectionList”List with single or multi-select:
const [selected, setSelected] = useState<string[]>([]);
<UISelectionList items={[ { value: "a", title: "Option A", subtitle: "First option" }, { value: "b", title: "Option B", subtitle: "Second option" }, ]} selected={selected} multiple={true} onChange={setSelected}/>Group components
Section titled “Group components”Groups are collapsible sections used for tree-like UIs (like the Tasks sidebar):
UIGroup
Section titled “UIGroup”A collapsible container with title, subtitle, meta, and actions:
const [expanded, setExpanded] = useState(true);
<UIGroup title="Project Alpha" subtitle="/Users/me/alpha" meta={<UIBadge label="3" tone="info" />} actions={<UIButton label="New" variant="ghost" size="sm" onClick={handleNew} />} expanded={expanded} onToggle={setExpanded}> {/* group body content */}</UIGroup>UIGroupList / UIGroupItem
Section titled “UIGroupList / UIGroupItem”Render items inside a group:
<UIGroup title="Tasks" expanded={true} onToggle={setExpanded}> <UIGroupList> <UIGroupItem title="Fix login bug" subtitle="in_progress" active={selectedId === "1"} onClick={() => selectTask("1")} /> <UIGroupItem title="Add dark mode" subtitle="todo" meta={<UIBadge label="P1" tone="warning" />} onClick={() => selectTask("2")} /> </UIGroupList></UIGroup>UIGroupEmpty
Section titled “UIGroupEmpty”Shown when a group has no items:
<UIGroup title="Tasks" expanded={true}> <UIGroupEmpty text="No tasks yet" /></UIGroup>Putting it all together
Section titled “Putting it all together”Here’s a complete sidebar that uses several components:
import { useState } from "react";import { defineReactInstrument, useInstrumentApi, UIRoot, UIPanelHeader, UISection, UICard, UIButton, UIInput, UIBadge, UIToggle, UIEmptyState,} from "tango-api";
function SidebarPanel() { const api = useInstrumentApi(); const [name, setName] = useState(""); const [greeting, setGreeting] = useState<string | null>(null); const [loud, setLoud] = useState(false);
const greet = () => { const msg = `Hello, ${name || "world"}!`; setGreeting(loud ? msg.toUpperCase() : msg); };
return ( <UIRoot> <UIPanelHeader title="Greeter" subtitle="UI Components demo" /> <UISection title="Configuration"> <UICard> <UIInput value={name} placeholder="Your name" onInput={setName} /> <UIToggle label="LOUD MODE" checked={loud} onChange={setLoud} /> <UIButton label="Greet" variant="primary" onClick={greet} /> </UICard> </UISection> <UISection title="Result"> {greeting ? ( <UICard> <UIBadge label={greeting} tone={loud ? "warning" : "success"} /> </UICard> ) : ( <UIEmptyState title="No greeting yet" description="Enter a name and click Greet" /> )} </UISection> </UIRoot> );}
export default defineReactInstrument({ defaults: { visible: { sidebar: true } }, panels: { sidebar: SidebarPanel },});Next steps
Section titled “Next steps”A single panel is useful but limited. In the next tutorial you’ll use two panels that communicate with each other.