apps/design-system/content/docs/ui-patterns/connect-interstitials.mdx
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 />
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.
import { OrganizationSelector } from '@/components/interfaces/Connect/OrganizationSelector'
import {
InterstitialAccountRow,
InterstitialLayout,
LogoBox,
LogoPair,
PartnerLogo,
SupabaseLogo,
} from '@/components/layouts/InterstitialLayout'
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.
<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>
<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>
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.
| Requester logo | Header treatment |
|---|---|
| Curated partner / MCP client, or a trusted uploaded icon | LogoPair with requester left, SupabaseLogo right |
| Unknown, missing, blocked, or failed-to-load icon | SupabaseLogo 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 />
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
Where logos come from on /authorize
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.SupabaseLogo alone.redirect_uri is a
remote host outside that partner's allowlist, show a caution admonition.
Localhost MCP redirects are excluded.Use InterstitialAccountRow for signed-in context. Do not recreate it locally.
<InterstitialAccountRow avatarUrl={avatarUrl} displayName={displayName} action={signOutButton} />
Use OrganizationSelector when the flow needs an organisation pick. Extend it
for new states instead of inventing a parallel card style.
<OrganizationSelector
organizations={linkableOrganizations}
selectedSlug={selectedOrgSlug}
onSelect={setSelectedOrgSlug}
createLabel="Create new organization"
onCreate={() => setShowOrgCreationDialog(true)}
/>
Prefer one full-width primary action. A full-width text button is fine for a secondary action that still belongs in the flow.
Match feedback to its scope:
FormMessage or FieldError beside a field when that field needs to
change.Admonition when the whole interstitial is blocked or has materially
changed state, such as an invalid link, wrong account, or partially completed
setup.<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 />
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 />
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.