RichAPI design system

Overlays and feedback

Menu

Click the button. The menu is a native popover.

JSX
<Menu trigger="Actions" label="Key actions">
  <MenuItem>Rename</MenuItem>
  <MenuItem kbd="R">Rotate key</MenuItem>
  <MenuSeparator />
  <MenuItem danger>Revoke</MenuItem>
</Menu>
HTML
<button type="button" class="ra-btn" popoverTarget="ra-menu-_R_0_" style="anchor-name:--ra-menu-_R_0_">Actions</button><div id="ra-menu-_R_0_" popover="auto" aria-label="Key actions" class="ra-menu" style="position-anchor:--ra-menu-_R_0_"><button type="button" class="ra-menu__item"><span>Rename</span></button><button type="button" class="ra-menu__item"><span>Rotate key</span><kbd class="ra-kbd">R</kbd></button><div role="separator" class="ra-menu__sep"></div><button type="button" class="ra-menu__item ra-menu__item--danger"><span>Revoke</span></button></div>

Menu panel

JSX
<MenuPanel label="Endpoints">
  <MenuSearch placeholder="Search endpoints" aria-label="Search endpoints" />
  <MenuItem hint="1 credit">Email finder</MenuItem>
  <MenuItem hint="1 credit">Domain lookup</MenuItem>
  <MenuSeparator />
  <MenuItem hint="3 credits">Company enrich</MenuItem>
</MenuPanel>
HTML
<div class="g-frame" style="max-width:280px"><div role="group" aria-label="Endpoints" class="ra-menu ra-menu--static"><div class="ra-menu__search"><input type="search" class="ra-menu__search-input" placeholder="Search endpoints" aria-label="Search endpoints"/></div><button type="button" class="ra-menu__item"><span>Email finder</span><small class="ra-menu__hint">1 credit</small></button><button type="button" class="ra-menu__item"><span>Domain lookup</span><small class="ra-menu__hint">1 credit</small></button><div role="separator" class="ra-menu__sep"></div><button type="button" class="ra-menu__item"><span>Company enrich</span><small class="ra-menu__hint">3 credits</small></button></div></div>

Tooltip

Hover or focus. Pure CSS, driven by data-tip.

JSX
<IconButton label="Add agent" icon={<Plus />} />
<Tooltip label="Live in production" side="bottom">
  <button type="button" style={{ all: "unset", cursor: "pointer" }}>
    <Pill>Live</Pill>
  </button>
</Tooltip>
HTML
<div class="g-frame g-frame--row" style="gap:24px;padding:32px 0"><button type="button" class="ra-btn ra-btn--ghost ra-btn--icon ra-tip" aria-label="Add agent" data-tip="Add agent"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M8 3v10M3 8h10"></path></svg></button><span class="ra-tip" data-tip="Live in production" data-side="bottom"><button type="button" style="all:unset;cursor:pointer"><span class="ra-pill">Live</span></button></span></div>

Dialog

A native modal dialog. Shown open in place here.

Rename key

JSX
<Dialog open onClose={() => {}} title="Rename key" footer={<Button onClick={() => {}}>Cancel</Button>
<Button variant="primary" onClick={() => {}}>Save</Button>}>
  <Input label="Key name" defaultValue="prod-agent" />
</Dialog>
HTML
<dialog class="ra-dialog" aria-labelledby="_R_0_-title" open=""><div class="ra-dialog__panel"><div class="ra-dialog__head"><h2 class="ra-dialog__title" id="_R_0_-title">Rename key</h2><button type="button" class="ra-btn ra-btn--ghost ra-btn--sm ra-btn--icon" aria-label="Close"><svg class="ra-icon ra-icon--sm" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button></div><div class="ra-dialog__body"><div class="ra-field"><label class="ra-field__label" for="_R_7_">Key name</label><input id="_R_7_" class="ra-input" value="prod-agent"/></div></div><div class="ra-dialog__foot"><button type="button" class="ra-btn">Cancel</button><button type="button" class="ra-btn ra-btn--primary">Save</button></div></div></dialog>

Type to confirm

confirmText keeps the confirm button disabled until the name is typed exactly.

Revoke "prod-agent"?

Every request using rk_live_••••4f2a fails immediately. This can't be undone.

JSX
<AlertDialog open title={"Revoke \"prod-agent\"?"} description="Every request using rk_live_••••4f2a fails immediately. This can't be undone." confirmLabel="Revoke key" confirmText="prod-agent" onConfirm={() => {}} onCancel={() => {}} />
HTML
<dialog class="ra-dialog ra-dialog--sm" aria-labelledby="_R_0_-title" aria-describedby="_R_0_-desc" open=""><div class="ra-dialog__panel"><div class="ra-dialog__head"><h2 class="ra-dialog__title" id="_R_0_-title">Revoke &quot;prod-agent&quot;?</h2><button type="button" class="ra-btn ra-btn--ghost ra-btn--sm ra-btn--icon" aria-label="Close"><svg class="ra-icon ra-icon--sm" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button></div><p class="ra-dialog__desc" id="_R_0_-desc">Every request using rk_live_••••4f2a fails immediately. This can&#x27;t be undone.</p><div class="ra-dialog__body"><div class="ra-field"><label class="ra-field__label" for="_R_7_">Type <b>prod-agent</b> to confirm</label><input id="_R_7_" class="ra-input" data-autofocus="true" autoComplete="off" value=""/></div></div><div class="ra-dialog__foot"><button type="button" class="ra-btn">Cancel</button><button type="button" class="ra-btn ra-btn--danger" disabled="">Revoke key</button></div></div></dialog>

Alert dialog

Revoke prod-agent?

Calls that use this key fail right away. You cannot undo this.

JSX
<AlertDialog open title="Revoke prod-agent?" description="Calls that use this key fail right away. You cannot undo this." confirmLabel="Revoke key" onConfirm={() => {}} onCancel={() => {}} />
HTML
<dialog class="ra-dialog ra-dialog--sm" aria-labelledby="_R_0_-title" aria-describedby="_R_0_-desc" open=""><div class="ra-dialog__panel"><div class="ra-dialog__head"><h2 class="ra-dialog__title" id="_R_0_-title">Revoke prod-agent?</h2><button type="button" class="ra-btn ra-btn--ghost ra-btn--sm ra-btn--icon" aria-label="Close"><svg class="ra-icon ra-icon--sm" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button></div><p class="ra-dialog__desc" id="_R_0_-desc">Calls that use this key fail right away. You cannot undo this.</p><div class="ra-dialog__foot"><button type="button" class="ra-btn" data-autofocus="true">Cancel</button><button type="button" class="ra-btn ra-btn--danger">Revoke key</button></div></div></dialog>

Primary alert dialog

Raise the rule to 150 cr?

Claude Code asks you above 150 cr from now on.

JSX
<AlertDialog open tone="primary" title="Raise the rule to 150 cr?" description="Claude Code asks you above 150 cr from now on." confirmLabel="Raise rule" onConfirm={() => {}} onCancel={() => {}} />
HTML
<dialog class="ra-dialog ra-dialog--sm" aria-labelledby="_R_0_-title" aria-describedby="_R_0_-desc" open=""><div class="ra-dialog__panel"><div class="ra-dialog__head"><h2 class="ra-dialog__title" id="_R_0_-title">Raise the rule to 150 cr?</h2><button type="button" class="ra-btn ra-btn--ghost ra-btn--sm ra-btn--icon" aria-label="Close"><svg class="ra-icon ra-icon--sm" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button></div><p class="ra-dialog__desc" id="_R_0_-desc">Claude Code asks you above 150 cr from now on.</p><div class="ra-dialog__foot"><button type="button" class="ra-btn" data-autofocus="true">Cancel</button><button type="button" class="ra-btn ra-btn--primary">Raise rule</button></div></div></dialog>

Command palette

Combobox and listbox in a native modal dialog. Shown open in place here.

Find work email1 credit
Find mobile phone10 credits
Company lookup
Usage
API keysG K
Billing
↑↓ to move, ↵ to select, Esc to close
JSX
<CommandPalette open onClose={() => {}} groups={[
  {
    label: "Endpoints",
    items: [
      {
        id: "email",
        label: "Find work email",
        hint: "1 credit",
        onSelect: () => {},
      },
      {
        id: "phone",
        label: "Find mobile phone",
        hint: "10 credits",
        onSelect: () => {},
      },
      {
        id: "company",
        label: "Company lookup",
        onSelect: () => {},
      },
    ],
  },
  {
    label: "Go to",
    items: [
      { id: "usage", label: "Usage", onSelect: () => {} },
      {
        id: "keys",
        label: "API keys",
        kbd: "G K",
        onSelect: () => {},
      },
      { id: "billing", label: "Billing", onSelect: () => {} },
    ],
  },
]} />
HTML
<dialog class="ra-palette" aria-label="Command menu" open=""><div class="ra-palette__search"><input class="ra-palette__input" role="combobox" aria-expanded="true" aria-controls="_R_0_-list" aria-activedescendant="_R_0_-opt-email" aria-autocomplete="list" placeholder="Search endpoints and pages" value=""/></div><div class="ra-palette__list" role="listbox" id="_R_0_-list"><div class="ra-palette__group" role="group" aria-label="Endpoints"><div class="ra-palette__group-label ra-label" aria-hidden="true">Endpoints</div><div id="_R_0_-opt-email" role="option" class="ra-palette__item" aria-selected="true">Find work email<small>1 credit</small></div><div id="_R_0_-opt-phone" role="option" class="ra-palette__item" aria-selected="false">Find mobile phone<small>10 credits</small></div><div id="_R_0_-opt-company" role="option" class="ra-palette__item" aria-selected="false">Company lookup</div></div><div class="ra-palette__group" role="group" aria-label="Go to"><div class="ra-palette__group-label ra-label" aria-hidden="true">Go to</div><div id="_R_0_-opt-usage" role="option" class="ra-palette__item" aria-selected="false">Usage</div><div id="_R_0_-opt-keys" role="option" class="ra-palette__item" aria-selected="false">API keys<kbd class="ra-kbd">G K</kbd></div><div id="_R_0_-opt-billing" role="option" class="ra-palette__item" aria-selected="false">Billing</div></div></div><div class="ra-palette__foot">↑↓ to move, ↵ to select, Esc to close</div></dialog>

Banners

Sandbox keys return sample data.
Your card was saved.
2.20 credits left. Calls stop at zero.
JSX
<div>
  <Banner>Sandbox keys return sample data.</Banner>
  <Banner tone="ok">Your card was saved.</Banner>
  <Banner tone="warn" actions={<Button variant="primary" size="sm" onClick={() => {}}>Add credits</Button>}>2.20 credits left. Calls stop at zero.</Banner>
  <Banner tone="err" actions={<Button onClick={() => {}}>Update card</Button>}>Your last payment failed.</Banner>
</div>
HTML
<div><div class="ra-banner" role="status"><div class="ra-banner__text"> Sandbox keys return sample data.</div></div><div class="ra-banner ra-banner--ok" role="status"><div class="ra-banner__text"> Your card was saved.</div></div><div class="ra-banner ra-banner--warn" role="status"><div class="ra-banner__text"> 2.20 credits left. Calls stop at zero.</div><div class="ra-banner__actions"><button type="button" class="ra-btn ra-btn--primary ra-btn--sm">Add credits</button></div></div><div class="ra-banner ra-banner--err" role="alert"><div class="ra-banner__text"> Your last payment failed.</div><div class="ra-banner__actions"><button type="button" class="ra-btn">Update card</button></div></div></div>

Pass banner

Enrichment Pass active. 41,820 cr left this month.
JSX
<Banner tone="pass" title="Enrichment Pass active.">41,820 cr left this month.</Banner>
HTML
<div class="ra-banner ra-banner--pass" role="status"><div class="ra-banner__text"><b>Enrichment Pass active.</b> 41,820 cr left this month.</div></div>

Dismissible banner

onDismiss adds a close button. A client component supplies the handler.

Your card was saved.
JSX
<Banner tone="ok" onDismiss={() => {}}>Your card was saved.</Banner>
HTML
<div class="ra-banner ra-banner--ok" role="status"><div class="ra-banner__text"> Your card was saved.</div><button type="button" class="ra-btn ra-btn--ghost ra-btn--sm ra-btn--icon ra-banner__dismiss" aria-label="Dismiss"><svg class="ra-icon ra-icon--sm" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button></div>

Toast with Undo

Revoked prod-agent
JSX
<div className="ra-toast-zone">
  <Toast action={{ label: "Undo", onAction: () => {} }}>Revoked prod-agent</Toast>
</div>
HTML
<div class="ra-toast-zone"><div class="ra-toast">Revoked prod-agent<button type="button" class="ra-toast__action">Undo</button></div></div>

Toasts

Static toasts in a zone that previews in place. ToastProvider renders the live zone.

Key copied
Run saved
The provider did not answer
JSX
<div className="ra-toast-zone">
  <Toast>Key copied</Toast>
  <Toast tone="ok">Run saved</Toast>
  <Toast tone="err">The provider did not answer</Toast>
</div>
HTML
<div class="ra-toast-zone"><div class="ra-toast">Key copied</div><div class="ra-toast ra-toast--ok">Run saved</div><div class="ra-toast ra-toast--err">The provider did not answer</div></div>

Empty state

No runs yet

Run your first call from Endpoints. It shows here with its request, response and receipt.

JSX
<EmptyState title="No runs yet" action={<Button variant="primary">Browse endpoints</Button>}>
  Run your first call from Endpoints. It shows here with its request, response and receipt.
</EmptyState>
HTML
<div class="ra-empty"><h3 class="ra-empty__title">No runs yet</h3><p class="ra-empty__text">Run your first call from Endpoints. It shows here with its request, response and receipt.</p><div class="ra-empty__action"><button type="button" class="ra-btn ra-btn--primary">Browse endpoints</button></div></div>

Error state

The provider timed out (504)

You were not charged. Try again, or pick another provider.

JSX
<EmptyState tone="err" title="The provider timed out (504)">You were not charged. Try again, or pick another provider.</EmptyState>
HTML
<div class="ra-empty ra-empty--err"><h3 class="ra-empty__title">The provider timed out (504)</h3><p class="ra-empty__text">You were not charged. Try again, or pick another provider.</p></div>

Skeleton

Loading
Loading
JSX
<Skeleton lines={3} />
<Skeleton height="120px" />
HTML
<div class="g-frame g-frame--stack" style="gap:16px"><div class="ra-skel-group" aria-busy="true"><span class="ra-sr-only">Loading</span><div class="ra-skel" aria-hidden="true"></div><div class="ra-skel" aria-hidden="true"></div><div class="ra-skel" aria-hidden="true" style="width:60%"></div></div><div class="ra-skel-group" aria-busy="true"><span class="ra-sr-only">Loading</span><div class="ra-skel" aria-hidden="true" style="height:120px"></div></div></div>

Hover card: run

Shown open here. In an app it opens on hover or focus.

run_8f2a91
LinkedIn job searchrun_8f2a91
Input"product designer" · Bengaluru
Result25 rows
Billed2.5 cr
Duration1.84s
WhenSep 11, 14:32
JSX
<HoverCard defaultOpen trigger={<a href="/runs/run_8f2a91">run_8f2a91</a>}>
  <div className="ra-hovercard__title">
    LinkedIn job search
    <Pill mono style={{ marginLeft: "auto" }}>run_8f2a91</Pill>
  </div>
  <Row k="Input" v={"\"product designer\" · Bengaluru"} />
  <Row k="Result" v="25 rows" />
  <Row k="Billed" v="2.5 cr" />
  <Row k="Duration" v="1.84s" />
  <Row k="When" v="Sep 11, 14:32" />
</HoverCard>
HTML
<div class="g-frame" style="min-height:190px"><span class="ra-hovercard"><a href="/runs/run_8f2a91" aria-describedby="_R_0_">run_8f2a91</a><span class="ra-hovercard__card" role="tooltip" id="_R_0_"><div class="ra-hovercard__title">LinkedIn job search<span class="ra-pill ra-pill--mono" style="margin-left:auto">run_8f2a91</span></div><div class="ra-hovercard__row"><span>Input</span><span>&quot;product designer&quot; · Bengaluru</span></div><div class="ra-hovercard__row"><span>Result</span><span>25 rows</span></div><div class="ra-hovercard__row"><span>Billed</span><span>2.5 cr</span></div><div class="ra-hovercard__row"><span>Duration</span><span>1.84s</span></div><div class="ra-hovercard__row"><span>When</span><span>Sep 11, 14:32</span></div></span></span></div>

Hover card: endpoint

email_finder
Email finder1 cr
Pricingper verified email
Endpoint/email_finder
On a miss0 cr
JSX
<HoverCard defaultOpen trigger={<a href="/endpoints/email_finder">email_finder</a>}>
  <div className="ra-hovercard__title">
    Email finder
    <Pill tone="brand" style={{ marginLeft: "auto" }}>1 cr</Pill>
  </div>
  <Row k="Pricing" v="per verified email" />
  <Row k="Endpoint" v="/email_finder" />
  <Row k="On a miss" v="0 cr" />
</HoverCard>
HTML
<div class="g-frame" style="min-height:190px"><span class="ra-hovercard"><a href="/endpoints/email_finder" aria-describedby="_R_0_">email_finder</a><span class="ra-hovercard__card" role="tooltip" id="_R_0_"><div class="ra-hovercard__title">Email finder<span class="ra-pill ra-pill--brand" style="margin-left:auto">1 cr</span></div><div class="ra-hovercard__row"><span>Pricing</span><span>per verified email</span></div><div class="ra-hovercard__row"><span>Endpoint</span><span>/email_finder</span></div><div class="ra-hovercard__row"><span>On a miss</span><span>0 cr</span></div></span></span></div>

Retry countdown

JSX
<RetryButton seconds={3} onRetry={() => {}} />
HTML
<button type="button" class="ra-btn" disabled="">Retry in 3s</button><span class="ra-sr-only" aria-live="polite"></span>

Drawer

A side sheet built on Dialog. Shown open in place here.

Run run_8f2a

Email finder

Status
Success
Credits
1
Key
Production
Duration
412 ms
JSX
<Drawer open onClose={() => {}} title="Run run_8f2a" description="Email finder" footer={<Button onClick={() => {}}>Close</Button>}>
  <KeyValue items={[
    { label: "Status", value: "Success" },
    { label: "Credits", value: "1" },
    { label: "Key", value: "Production" },
    { label: "Duration", value: "412 ms", mono: true },
  ]} />
</Drawer>
HTML
<dialog class="ra-dialog ra-drawer ra-drawer--end" aria-labelledby="_R_0_-title" aria-describedby="_R_0_-desc" open=""><div class="ra-dialog__panel"><div class="ra-dialog__head"><h2 class="ra-dialog__title" id="_R_0_-title">Run run_8f2a</h2><button type="button" class="ra-btn ra-btn--ghost ra-btn--sm ra-btn--icon" aria-label="Close"><svg class="ra-icon ra-icon--sm" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button></div><p class="ra-dialog__desc" id="_R_0_-desc">Email finder</p><div class="ra-dialog__body"><dl class="ra-kv"><div class="ra-kv__row"><dt class="ra-kv__label">Status</dt><dd class="ra-kv__value">Success</dd></div><div class="ra-kv__row"><dt class="ra-kv__label">Credits</dt><dd class="ra-kv__value">1</dd></div><div class="ra-kv__row"><dt class="ra-kv__label">Key</dt><dd class="ra-kv__value">Production</dd></div><div class="ra-kv__row"><dt class="ra-kv__label">Duration</dt><dd class="ra-kv__value ra-kv__value--mono">412 ms</dd></div></dl></div><div class="ra-dialog__foot"><button type="button" class="ra-btn">Close</button></div></div></dialog>

Drawer, start side

side="start" pins the sheet to the left edge.

Filters

JSX
<Drawer open side="start" onClose={() => {}} title="Filters" footer={<Button onClick={() => {}}>Done</Button>}>
  <Input label="Endpoint" defaultValue="Email finder" />
</Drawer>
HTML
<dialog class="ra-dialog ra-drawer ra-drawer--start" aria-labelledby="_R_0_-title" open=""><div class="ra-dialog__panel"><div class="ra-dialog__head"><h2 class="ra-dialog__title" id="_R_0_-title">Filters</h2><button type="button" class="ra-btn ra-btn--ghost ra-btn--sm ra-btn--icon" aria-label="Close"><svg class="ra-icon ra-icon--sm" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button></div><div class="ra-dialog__body"><div class="ra-field"><label class="ra-field__label" for="_R_7_">Endpoint</label><input id="_R_7_" class="ra-input" value="Email finder"/></div></div><div class="ra-dialog__foot"><button type="button" class="ra-btn">Done</button></div></div></dialog>

Popover, live

Click the button. A native popover that focuses its first control.

JSX
<Popover label="Rename key" trigger="Rename">
  <Input label="Key name" defaultValue="Production" autoFocus />
</Popover>
HTML
<button type="button" class="ra-btn" popoverTarget="ra-pop-_R_0_" aria-haspopup="dialog" style="anchor-name:--ra-pop-_R_0_">Rename</button><div id="ra-pop-_R_0_" popover="auto" role="dialog" aria-label="Rename key" class="ra-popover" style="position-anchor:--ra-pop-_R_0_"><div class="ra-field"><label class="ra-field__label" for="_R_5_">Key name</label><input id="_R_5_" class="ra-input" autofocus="" value="Production"/></div></div>

Popover

PopoverPanel is the static version.

JSX
<PopoverPanel label="Key details">
  <KeyValue items={[
    { label: "Created", value: "Sep 9" },
    { label: "Last used", value: "2 min ago" },
    { label: "Scope", value: "All endpoints" },
  ]} />
</PopoverPanel>
HTML
<div class="g-frame" style="max-width:260px"><div role="dialog" aria-label="Key details" class="ra-popover ra-popover--static"><dl class="ra-kv"><div class="ra-kv__row"><dt class="ra-kv__label">Created</dt><dd class="ra-kv__value">Sep 9</dd></div><div class="ra-kv__row"><dt class="ra-kv__label">Last used</dt><dd class="ra-kv__value">2 min ago</dd></div><div class="ra-kv__row"><dt class="ra-kv__label">Scope</dt><dd class="ra-kv__value">All endpoints</dd></div></dl></div></div>

Progress

JSX
<Progress label="Bulk run, 30 of 120 rows" value={30} max={120} />
<Progress label="Credits used, 85 of 100" value={85} tone="warn" />
<Progress label="Quota used, 100 of 100" value={100} tone="err" />
<Progress label="Uploading" />
HTML
<div class="g-frame g-frame--md g-frame--stack" style="gap:16px"><div class="ra-progress" role="progressbar" aria-label="Bulk run, 30 of 120 rows" aria-valuemin="0" aria-valuemax="120" aria-valuenow="30"><i class="ra-progress__fill" style="width:25%"></i></div><div class="ra-progress ra-progress--warn" role="progressbar" aria-label="Credits used, 85 of 100" aria-valuemin="0" aria-valuemax="100" aria-valuenow="85"><i class="ra-progress__fill" style="width:85%"></i></div><div class="ra-progress ra-progress--err" role="progressbar" aria-label="Quota used, 100 of 100" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100"><i class="ra-progress__fill" style="width:100%"></i></div><div class="ra-progress ra-progress--indeterminate" role="progressbar" aria-label="Uploading"><i class="ra-progress__fill"></i></div></div>

Spinner

Loading resultsLoading
JSX
<Spinner size="sm" label="Loading results" />
<Spinner />
HTML
<div class="g-frame g-frame--row" style="gap:16px;flex-wrap:nowrap"><span class="ra-spinner ra-spinner--sm" role="status"><span class="ra-sr-only">Loading results</span></span><span class="ra-spinner" role="status"><span class="ra-sr-only">Loading</span></span></div>