Building interfaces
Render views inside the Pivit command bar using the SDK's layout, text and result primitives.
Interface commands return React elements that Pivit renders inside the command bar. You do not style them yourself — @pivit/api exports primitives that already match the user's theme, so a plugin looks native under all eight themes and in both light and dark mode.
A basic view
import { EmptyState, Result, Section, View } from "@pivit/api";
function IssueList({ issues }: { issues: Issue[] }) {
if (issues.length === 0) {
return <EmptyState title="No issues found" icon="circle-dot" />;
}
return (
<View>
<Section title="Assigned to you">
{issues.map((issue) => (
<Result
key={issue.id}
title={issue.title}
subtitle={issue.repository}
icon="circle-dot"
onSelect={() => openUrl(issue.url)}
/>
))}
</Section>
</View>
);
}Available primitives
Layout
| Component | Purpose |
|---|---|
View | Root container for a command's interface |
Section | Groups results under a heading |
Stack | Vertical or horizontal grouping |
Row | A single horizontal line of content |
Divider | Visual separator |
Content
| Component | Purpose |
|---|---|
Result | A selectable row — the workhorse of most plugins |
Heading | Section or view title |
Text | Body copy |
KeyValue | Label and value pairs, for detail views |
Badge | Small status or category marker |
Code | Inline or block code |
EmptyState | Shown when there is nothing to display |
Input and actions
| Component | Purpose |
|---|---|
Input | Text entry inside a view |
Action | An explicit action attached to a result or view |
Command | Compose a nested command surface |
Keep views shallow
The command bar is a transient surface. Someone opened it to get an answer and leave. Two principles follow from that:
- One screen where possible. If a plugin needs several steps, consider whether the first step could be the whole thing.
- Selectable results beat prose.
Resultrows are keyboard navigable for free. A wall ofTextis not.
Loading and errors
Interface commands can be async, so fetch before returning if the data is quick. For anything slower, return a view immediately and let it fill in — a bar that appears instantly and populates beats one that hangs on a network call.
open: async (input) => {
return <IssueList query={input} />;
};Handle failure inside the view rather than throwing. EmptyState accepts a title and description, which makes it a reasonable error surface as well as an empty one.