Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

React Components

@launchmystore/app-bridge-react ships two kinds of components:
  1. Chrome wrappers<TitleBar />, <NavigationMenu />, <Loading />. These render nothing visible. They render to the host chrome by calling the matching App Bridge action under the hood.
  2. In-iframe UI primitives<AdminBlock>, <AdminAction>, <AdminPrintAction>, layout/text/form primitives. These render inside your extension iframe and give you a consistent look without pulling in a separate design system.
All components require an <AppBridgeProvider> ancestor — see React Hooks.

Chrome wrappers

These are declarative shells around their matching hooks. They return null and re-render the host chrome whenever their props change. Use them when you’d rather think in JSX than in imperative .update() calls.

<TitleBar />

Sets the admin page title, primary/secondary action buttons, and breadcrumbs. Wraps useTitleBar.
import { TitleBar } from '@launchmystore/app-bridge-react';

export default function ProductPage({ product }) {
  const [saving, setSaving] = useState(false);

  return (
    <>
      <TitleBar
        title={product.title}
        breadcrumbs={[{ label: 'Products', url: '/products' }]}
        primaryAction={{
          label: 'Save',
          loading: saving,
          onAction: async () => {
            setSaving(true);
            await saveProduct(product);
            setSaving(false);
          },
        }}
        secondaryActions={[
          { label: 'Duplicate', onAction: duplicate },
          { label: 'Delete', destructive: true, onAction: remove },
        ]}
      />
      <ProductForm product={product} />
    </>
  );
}
Props (TitleBarProps)
PropTypeDescription
titlestringRequired. Shown as the page title in the host admin chrome.
primaryAction{ label, onAction?, disabled?, loading?, destructive? }Right-aligned filled button.
secondaryActionsArray<{ label, onAction?, disabled?, destructive? }>Plain buttons left of the primary.
breadcrumbsArray<{ label, url? | destination? }>Rendered as a chevron-separated trail before the title.
Re-renders trigger an update() on the underlying action — flip loading: true on the primary action to show a spinner without re-creating the bar.

<NavigationMenu />

Registers a left-rail navigation menu for the app’s admin pages. Wraps useNavigationMenu.
import { NavigationMenu } from '@launchmystore/app-bridge-react';
import { useRouter } from 'next/router';

export default function AppLayout({ children }) {
  const router = useRouter();
  return (
    <>
      <NavigationMenu
        items={[
          { label: 'Dashboard', url: '/' },
          { label: 'Campaigns', url: '/campaigns', badge: 'New' },
          { label: 'Settings', url: '/settings' },
        ]}
        active={router.pathname}
        onNavigate={(url) => router.push(url)}
      />
      {children}
    </>
  );
}
Props (NavigationMenuProps)
PropTypeDescription
itemsArray<{ label, url, disabled?, icon?, badge? }>Required. Order = render order.
activestringURL of the currently-active item — host highlights it.
onNavigate(url: string) => voidCalled when the user clicks an item. Use this to push to your router.
The host swallows the click and fires this callback — you’re responsible for the in-iframe route change (e.g. router.push(url)).

<Loading />

Toggles the global loading bar in the host chrome (the thin progress strip at the top of the admin). Wraps useLoading.
import { Loading } from '@launchmystore/app-bridge-react';

function ProductDetail({ id }) {
  const { data, isLoading } = useAppQuery(`/api/v1/products/${id}`);

  return (
    <>
      <Loading loading={isLoading} />
      {data ? <ProductView product={data} /> : null}
    </>
  );
}
Props (LoadingProps)
PropTypeDefaultDescription
loadingbooleantrueWhen true, the host loading bar is shown. The bar hides on unmount automatically.

Contextual save bar

There’s no <ContextualSaveBar /> component — the save bar’s imperative API (setSaveLoading, setDiscardLoading) is a poor fit for declarative JSX. Use the useContextualSaveBar or useDirtyState hooks instead:
import { useDirtyState, TextField } from '@launchmystore/app-bridge-react';

function SettingsForm({ initial }) {
  const [form, setForm] = useState(initial);
  const { setDirty } = useDirtyState({
    onSave: () => saveSettings(form),
    onDiscard: () => setForm(initial),
  });

  return (
    <TextField
      label="Store name"
      value={form.name}
      onChange={(name) => {
        setForm((s) => ({ ...s, name }));
        setDirty(true);
      }}
    />
  );
}

Admin extension containers

Use these as the outermost element of any extension iframe. They give you a card / modal / print layout that matches the host admin look without you having to import a design system.

<AdminBlock>

Outer container for block extension targets (product.details.block, order.details.block, etc.). Renders a white card with a title and padding.
import { AdminBlock, BlockStack, Text, Badge } from '@launchmystore/app-bridge-react';

export default function Reviews() {
  return (
    <AdminBlock title="Reviews">
      <BlockStack gap="200">
        <Text variant="headingLg">4.8 / 5</Text>
        <Text tone="subdued">124 customer reviews</Text>
        <Badge tone="success">Verified app</Badge>
      </BlockStack>
    </AdminBlock>
  );
}
Props (AdminBlockProps)title?: string, padding?: number (default 16), plus any HTMLDivElement attributes.

<AdminAction>

Container for modal-style action extension targets (admin.order-details.action.render, etc.). Renders a scrolling body with a sticky footer containing the primary + secondary action buttons. The footer handles button states (loading / destructive / disabled) for you.
import { AdminAction, BlockStack, TextField } from '@launchmystore/app-bridge-react';

export default function RefundDialog({ order, onClose }) {
  const [amount, setAmount] = useState('');
  const [submitting, setSubmitting] = useState(false);

  return (
    <AdminAction
      primaryAction={{
        content: 'Issue refund',
        destructive: true,
        loading: submitting,
        onAction: async () => {
          setSubmitting(true);
          await issueRefund(order.id, amount);
          onClose();
        },
      }}
      secondaryActions={[
        { content: 'Cancel', onAction: onClose },
      ]}
    >
      <BlockStack gap="300">
        <TextField
          label="Refund amount"
          value={amount}
          onChange={setAmount}
        />
      </BlockStack>
    </AdminAction>
  );
}
Props (AdminActionProps)
  • primaryAction?: { content, onAction?, loading?, destructive?, disabled? }
  • secondaryActions?: Array<{ content, onAction?, disabled? }>
  • Plus any HTMLDivElement attributes.

<AdminPrintAction>

Container for print extension targets (admin.order-details.print.render). Renders a full-page print layout with @media print rules that reset margins and colors. Use usePrintReady() from inside the tree to signal the host that the content has finished loading and can be sent to the printer.
import { AdminPrintAction, usePrintReady, Heading, Text } from '@launchmystore/app-bridge-react';

export default function PackingSlip({ order }) {
  const { setReady } = usePrintReady();
  const { data } = useAppQuery(`/api/v1/orders/${order.id}`);
  useEffect(() => { if (data) setReady(true); }, [data]);

  if (!data) return null;
  return (
    <AdminPrintAction>
      <Heading level={1}>Packing slip</Heading>
      <Text>Order #{data.order_number}</Text>
    </AdminPrintAction>
  );
}
The host inspects data-print-ready="true" on the container and only then calls the browser print dialog.

Layout primitives

Token-driven flex layouts. Gaps use the spacing scale "0" | "100" | "200" | "300" | "400" | "500" (= 0 | 4 | 8 | 12 | 16 | 24 px).
ComponentPurpose
<BlockStack>Vertical flex column. gap defaults to "200".
<InlineStack>Horizontal flex row. gap, align, blockAlign.
<Box>Generic <div> with token padding / background.
<Divider>Thin horizontal rule using tokens.color.border.
<BlockStack gap="300">
  <Text variant="headingMd">Order #1234</Text>
  <InlineStack gap="200" align="space-between">
    <Text>Total</Text>
    <Text fontWeight="semibold">$99.00</Text>
  </InlineStack>
  <Divider />
</BlockStack>

Typography

ComponentPurpose
<Text>Body / heading text. variant (bodyXsbodyLg, headingSmheadingXl), tone (base, subdued, critical, success, caution), fontWeight, as.
<Heading>h1h6 with level: 1-6. Wraps <Text> with sensible defaults.
<Link>Anchor with token color + hover underline. Pass external for target="_blank" rel="noreferrer".
<Heading level={2}>Inventory</Heading>
<Text tone="subdued">Last synced 3 minutes ago.</Text>
<Link external href="https://launchmystore.io/docs">Read the docs</Link>

Display

ComponentPurpose
<Banner>Tone-coloured callout. tone: 'info' | 'warning' | 'critical' | 'success', title, onDismiss.
<Badge>Pill label. tone (info, success, warning, critical, attention, new), size: 'small' | 'medium'.
<Image><img> with rounded corners + lazy-loading default.
<Icon>Inline SVG icon. Pass a source SVG path or use the small built-in set.
<EmptyState>Centered “nothing here yet” block with optional <Button> action.
<ProgressIndicator>Determinate progress bar. value: 0-100.
<Spinner>Indeterminate circular spinner. size: 'small' | 'large'.
<Banner tone="critical" title="Sync failed" onDismiss={dismiss}>
  Couldn't reach the upstream provider. We'll retry in 5 minutes.
</Banner>

Form

Controlled inputs. All call onChange(value) with the next value (no event object). All accept label, helpText, error.
ComponentPurpose
<TextField><input> / <textarea> (multiline). Supports prefix / suffix slots, type: 'text' | 'email' | 'password' | 'search' | 'tel' | 'url' | 'number'.
<NumberField>TextField with inputMode="numeric" + numeric onChange. min, max, step.
<MoneyField>TextField with currency prefix. currency: 'USD' (etc.).
<DateField><input type="date"> returning ISO YYYY-MM-DD.
<Select>Native <select>. options: Array<{ label, value }>.
<Checkbox>Single boolean.
<ChoiceList>Radio (single) or checkbox (allowMultiple) group. choices: Array<{ label, value, helpText? }>.
<Button>variant: 'primary' | 'secondary' | 'tertiary' | 'plain', tone: 'base' | 'critical' | 'success', size, loading.
<Form><form> that calls onSubmit(e) and prevents default. Useful for Enter to submit.
import { Form, TextField, MoneyField, Select, Button, BlockStack } from '@launchmystore/app-bridge-react';

function CreateDiscount() {
  const [code, setCode] = useState('');
  const [amount, setAmount] = useState('10');
  const [kind, setKind] = useState('percentage');

  return (
    <Form onSubmit={() => createDiscount({ code, amount, kind })}>
      <BlockStack gap="300">
        <TextField
          label="Discount code"
          value={code}
          onChange={setCode}
          helpText="Customers enter this at checkout"
        />
        <Select
          label="Kind"
          value={kind}
          onChange={setKind}
          options={[
            { label: 'Percentage off', value: 'percentage' },
            { label: 'Fixed amount off', value: 'fixed' },
          ]}
        />
        {kind === 'percentage'
          ? <NumberField label="Percent off" value={amount} onChange={setAmount} min={0} max={100} />
          : <MoneyField label="Amount off" value={amount} onChange={setAmount} currency="USD" />}
        <Button variant="primary">Create</Button>
      </BlockStack>
    </Form>
  );
}

Design tokens

All components read from a shared token set in packages/app-bridge-react/src/components/tokens.ts:
  • tokens.color.* — base, surface, border, primary, critical, success, info, warning, plus *Subdued / *Border variants for banners.
  • tokens.font.*family, size.{xs,sm,md,lg,xl}, weight.*, lineHeight.*.
  • tokens.radius.*sm (4), md (6), lg (8).
  • tokens.shadow.*sm for cards.
You can override per-component via the standard style prop — the token style is spread first, your style last.

When to use components vs. hooks

GoalUse
Set the title bar declaratively<TitleBar />
Toggle the title bar’s primaryAction.loadingEither — the component re-renders.
Open a confirmation modal once on clickuseConfirmationModal hook
Render a scrolling-with-sticky-footer action page<AdminAction>
Show / hide the host loading bar<Loading loading={...} />
Manage a contextual save bar tied to form stateuseDirtyState hook
Open a resource picker imperativelyuseResourcePicker / useApi().picker
Both styles use the same wire protocol — pick whichever reads better in the component you’re writing.

See Also

  • React Hooks — every hook the components wrap, plus the data-fetching / cart / lifecycle hooks that have no component equivalent.
  • Actions Reference — raw payload shapes for every App Bridge action.
  • App Bridge Overview — the iframe ↔ host postMessage contract underneath all of this.