Back to Supabase

Connect Interstitials

apps/design-system/content/docs/ui-patterns/connect-interstitials.mdx

1.26.088.8 KB
Original Source

Connect interstitials are focused, single-card flows that sit outside the main Studio shell. Use the shared InterstitialLayout family instead of building bespoke centered cards, logos, account rows, or organisation selectors.

<ComponentPreview name="connect-interstitial-demo" description="Centered 400px card with partner branding, account row, and a single primary action" align="start" className="p-0" padded={false} peekCode wide />

Use this pattern for

This pattern fits short-lived connect flows: partner authorisation and consent (OAuth, MCP, Stripe Projects), organisation invites, marketplace and billing connections (AWS Marketplace, Vercel install, credit redemption), and CLI or device-code sign-in. Use the same shell for their loading, error, success, and wrong-account states.

Do not use it for normal authenticated Studio pages. Those should use the standard page layout patterns.

Source of truth

tsx
import { OrganizationSelector } from '@/components/interfaces/Connect/OrganizationSelector'
import {
  InterstitialAccountRow,
  InterstitialLayout,
  LogoBox,
  LogoPair,
  PartnerLogo,
  SupabaseLogo,
} from '@/components/layouts/InterstitialLayout'

Basic shape

Use InterstitialLayout for the outer card, then put route-specific content in px-6 pb-6. Widen the card only when the flow embeds a real tool, such as project linking.

tsx
<InterstitialLayout
  logo={
    <LogoPair
      left={<PartnerLogo src={`${BASE_PATH}/img/icons/stripe-icon.svg`} alt="Stripe" />}
      right={<SupabaseLogo />}
    />
  }
  title="Authorize Stripe Projects"
  description="This will create an organization on your behalf in Supabase"
>
  <div className="px-6 pb-6">
    <InterstitialAccountRow displayName={displayName} />
    <Button variant="primary" block>
      Continue
    </Button>
  </div>
</InterstitialLayout>
tsx
<InterstitialLayout
  logo={<LogoPair left={<VercelLogo />} right={<SupabaseLogo />} />}
  title="Connect Vercel project"
  containerClassName="items-start"
  cardClassName="max-w-[900px]"
>
  <div className="px-6 pb-6">{projectLinker}</div>
</InterstitialLayout>

Logos

Use LogoPair when the user is connecting two known services, and SupabaseLogo alone for first-party flows or when the requester has no trusted mark. PartnerLogo fills the 48px box edge-to-edge; LogoBox is for custom inset marks or logos that need their own background. Store new partner icons in apps/studio/public/img/icons.

Pairing

Requester logoHeader treatment
Curated partner / MCP client, or a trusted uploaded iconLogoPair with requester left, SupabaseLogo right
Unknown, missing, blocked, or failed-to-load iconSupabaseLogo alone. Do not invent an initial tile.

The user is usually arriving from the third-party app. The interstitial should confirm they are connecting to Supabase. Put the requester name in the title and description; do not manufacture a letter avatar to fill the left side of a pair.

Known services. When both sides are curated (or otherwise known), pair them. Theme-reactive tiles are fine when both marks have matching light/dark treatment.

<ComponentPreview name="connect-interstitial-logo-pair" description="LogoPair when the user is connecting two services" align="start" className="p-0" padded={false} peekCode wide />

No trusted requester mark. If the icon is missing, blocked, or fails to load, show SupabaseLogo alone. Do not invent an initial tile to fill the pair.

<ComponentPreview name="connect-interstitial-logo-unknown" description="No trusted requester mark: Supabase alone" align="start" className="p-0" padded={false} peekCode wide />

Uploaded organisation OAuth icons. Icons published via Studio’s OAuth app builder are unclassified bitmaps — we do not know if they were authored for light or dark. Treat the pair as light on both Studio themes: fixed light tile chrome (border-black/10 bg-white, SupabaseLogo forceLight) on both sides. Do not invent a dark variant for the upload. Toggle the docs theme to dark to see the light tiles hold against the Studio chrome.

<ComponentPreview name="connect-interstitial-logo-uploaded" description="Uploaded OAuth app icon: forced-light tiles on both sides" align="start" className="p-0" padded={false} peekCode wide />

Assets

Treat Connect logos as assets, not theme tokens.

Default to light. Prefer a single static light mark inside LogoBox. Light assets read fine on both light and dark Studio themes. That is the default for Connect tiles.

Keep pairs matched. In a LogoPair, both marks must use the same treatment: both light, or both dark. Do not mix a light partner tile with a dark-theme-only Supabase treatment, or the reverse. Theme-aware dark variants are fine for curated partners that already have them, but then both sides of the pair should use the dark set together.

What not to do

  • Do not invent light/dark pairs for arbitrary remote OAuth icons.
  • Do not recolour vendor SVGs with theme CSS. Monochrome identity-provider masks (for example GitHub on sign-in) stay a separate pattern.

Where logos come from on /authorize

  • Curated partner logos resolve from allowlisted redirect_uri hosts, or from a trusted partner name when redirect_uri is localhost / loopback (local MCP clients). Do not resolve curated logos from self-asserted name or website on a remote host. Those pairs may use theme tiles and dark assets when the partner has them.
  • Published organisation OAuth app icons uploaded in Studio remain trusted remote images, paired with forced-light tiles on both sides.
  • Everything else falls back to SupabaseLogo alone.
  • If the requester name looks like a known partner but redirect_uri is a remote host outside that partner's allowlist, show a caution admonition. Localhost MCP redirects are excluded.

Account row

Use InterstitialAccountRow for signed-in context. Do not recreate it locally.

tsx
<InterstitialAccountRow avatarUrl={avatarUrl} displayName={displayName} action={signOutButton} />

Organisation selection

Use OrganizationSelector when the flow needs an organisation pick. Extend it for new states instead of inventing a parallel card style.

tsx
<OrganizationSelector
  organizations={linkableOrganizations}
  selectedSlug={selectedOrgSlug}
  onSelect={setSelectedOrgSlug}
  createLabel="Create new organization"
  onCreate={() => setShowOrgCreationDialog(true)}
/>

Actions

Prefer one full-width primary action. A full-width text button is fine for a secondary action that still belongs in the flow.

Action feedback

Match feedback to its scope:

  • Use FormMessage or FieldError beside a field when that field needs to change.
  • Show a submission or action failure as simple destructive text below the actions. Separate it with a subtle divider when needed for composition. Keep the current account, selections, and actions visible so the user can retry.
  • Use Admonition when the whole interstitial is blocked or has materially changed state, such as an invalid link, wrong account, or partially completed setup.
  • Use a toast only for non-blocking feedback or a completed action whose originating surface is no longer visible. A toast must not be the only feedback for a failure the user needs to resolve on the current card.
tsx
<InterstitialActionError error={actionError} />

Clear stale action feedback when the user retries or changes a relevant selection. Error copy should say what failed and, when it is not obvious, what the user can do next. When passive supporting copy occupies the same footer region, replace it with the action error until the error is cleared instead of stacking both messages.

<ComponentPreview name="connect-interstitial-action-error" description="Retryable action error shown beside the actions" align="start" className="p-0" padded={false} peekCode wide />

States

Keep loading, invalid, error, and success states inside the same card when the route can explain them. Use ShimmeringLoader for loading, and Admonition for warning, error, note, and success copy.

<ComponentPreview name="connect-interstitial-logo-single" description="Wrong-account warning inside the same interstitial card" align="start" className="p-0" padded={false} peekCode wide />

Copy

Use sentence case. Prefer sign in over login. Titles and primary actions should follow Verb -> Thing, for example Authorize Stripe Projects or Install Vercel.

Keep the layout title static across states and put state-specific copy in the body. Header descriptions should stay short and should not end with a full stop.