Pivit

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

ComponentPurpose
ViewRoot container for a command's interface
SectionGroups results under a heading
StackVertical or horizontal grouping
RowA single horizontal line of content
DividerVisual separator

Content

ComponentPurpose
ResultA selectable row — the workhorse of most plugins
HeadingSection or view title
TextBody copy
KeyValueLabel and value pairs, for detail views
BadgeSmall status or category marker
CodeInline or block code
EmptyStateShown when there is nothing to display

Input and actions

ComponentPurpose
InputText entry inside a view
ActionAn explicit action attached to a result or view
CommandCompose 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. Result rows are keyboard navigable for free. A wall of Text is 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.

On this page