The four pages in this section cover the components you assemble a product
out of. This page is the index — and the home of the handful that belong to
no group.

## Where each one is covered

| Group | Components | Page |
| --- | --- | --- |
| Shell and navigation | `ConnectAppShell`, `ConnectSidebar*`, `ConnectSidebarNav`, `ConnectNav`, `ConnectUserButton`, `ConnectProfileModal`, `ConnectLogo`, `ConnectMark` | [Shell](/docs/ui/shell) |
| Gates | `ConnectAccessGate`, `ConnectFeatureGate`, `ConnectReadinessGate`, `ConnectSubscriptionGate`, `ConnectPlatformGate`, `ConnectWorkspaceTypeGate` | [Gates](/docs/ui/gates) |
| Screens and widgets | `ConnectPageShell`, `ConnectSection`, `ConnectEmptyState`, `ConnectSettingRow`, `ConnectStatusChip`, `ConnectFilterTabs`, `ConnectUsageMeter`, `ConnectSeatUsage`, `ConnectTrialBanner`, `ConnectGrievanceNotice` | [Screens](/docs/ui/screens) |
| Workspaces | `ConnectWorkspaceSwitcher`, `ConnectAppsGrid` | [Workspaces](/docs/ui/workspaces) |
| Plans and billing | `ConnectPlans`, `ConnectPlansPage`, `ConnectSubscribeDialog`, `ConnectSubscriptionCard`, `ConnectReadinessBanner` | [Screens](/docs/ui/screens) |
| Sign-in | `ConnectLoginButton`, `ConnectRegisterButton`, `ConnectSignOutButton`, `ConnectOneTap` | [Authentication](/docs/authentication) |
| Assistants | `ConnectAskAi` | [AI](/docs/ai) |
| | `ConnectVisitorChat` | [Visitor assistant](/docs/visitor) |
| | `ConnectLinkButton` | Below, on this page |

Props for all of them are in the generated
[client reference](/docs/reference/client); the six presentational ones that
render in a Server Component are in [UI](/docs/reference/ui).

## The ones with no group

### `ConnectLinkButton`

The only supported way to render a link that looks like a button. HeroUI v3's
`Button` has no `as` prop, so `<Button as={Link}>` neither type-checks nor
works — this goes through HeroUI's `render` instead:

```tsx
import Link from "next/link";
import { ConnectLinkButton } from "@cosmoops/connect-sdk/client";

<ConnectLinkButton href="/deals" renderLink={(props) => <Link {...props} />}>
  Open board
</ConnectLinkButton>
```

**Pass `renderLink` whenever your router has one.** Without it this falls back
to a plain `<a href>`, which navigates under any router but costs a full
document load — in an App Router app that throws away the client cache and
re-runs the whole tree, very visible on a button sitting inside a page the
user is already working in. Same shape as `AppShell`'s own `renderLink`, so a
host declares its router once and hands the same function to both.

### `ConnectConfirmDialog`

The confirmation in front of a destructive action.

**It closes on confirm, not on response.** Holding the modal open until the
server answers makes every delete feel like a form submission, and the error
path already has somewhere to go — a toast, or the row reappearing. A dialog
that stays open to report failure is right only where the failure is
*expected* and correctable in place (a seat cap, a last-admin invariant), and
that is a blocking form, not this.

`onConfirm` may return a promise. It is not awaited before closing, but a
rejection is caught rather than escaping — an error thrown out of an async
transition reaches the nearest error boundary and takes the page down.

<Callout type="warn" title="The trigger is a direct child">
Pass your button as a direct child, never wrapped in `Modal.Trigger`. That
wrapping renders a `div role="button"` inside React Aria's `Pressable` and
relies on a plain `onClick` bubbling up; a React Aria button nested inside
calls `stopPropagation()` in its own press handler, so the click never
arrives and the dialog silently never opens.
</Callout>

### `ConnectCursorPagination`

Next / previous for a cursor-paginated list. There is no "page 7 of 42"
because a keyset-paginated list cannot know how many pages there are without a
second count query over the same filter — a billed index scan for a number
nobody acts on. What a user does act on is *more* and *back*, and those are
exact.

The row count is announced in a live region, so a screen-reader user learns
that filtering or paging changed the result set. Without it the table silently
swaps its contents.

### `ConnectNoSubscriptionCard`

The "you are not on a plan yet" card. It points at **your own** `/plans` page
rather than opening Connect: choosing a plan is browsing, it belongs in your
product where your pricing story is, and only the final confirmation needs to
happen on Connect. `plansHref` is overridable, because not every app mounts its
catalogue at `/plans`.

Rendered on its own it is just a card. `ConnectSubscriptionGate` is the usual
way in, since it picks this only when there is genuinely nothing to show.

### `ConnectAccountSessions`

Every device signed into this **Connect account** — not scoped to your
application, because a Connect session is one browser's login to Connect
itself and can back sign-ins to every application the user has authorized from
it. Current device first, then most recently active.

It owns the one `useConnectSessions()` call for the modal it sits in, and each
revoke re-reads the list itself; nothing invalidates it automatically.

### `ConnectIntegrationsModal`

A grid of every Composio-hosted toolkit the workspace can connect — Google
Calendar, Slack, and whatever else a platform admin enabled in Connect's
Console. Each tile carries its own connect / reconnect / disconnect action and
its own pending state, independent of the others.

**Not optimistic**, deliberately: a tile stays as it was after Connect is
pressed, because the connection is not settled until Composio's webhook
confirms it — and the browser has navigated away to the provider's OAuth
screen by then anyway. Renders nothing when signed out, same as
`ConnectAppsGrid`.

### `ConnectBookingCalendar`, `ConnectBookingDialog`, `ConnectBookingDetail`

The scheduling trio, over a subscribing workspace's own diary.

- **`ConnectBookingDialog`** — picking a time, then who it is for. The slot
  list is read fresh on every open and **never cached across one**: it is a
  snapshot of contention, and offering a time somebody took ninety seconds ago
  is how two people end up in the same slot. The server re-checks inside its
  transaction regardless, so a stale list costs a refusal rather than a double
  booking — this only makes the refusal rare enough to be surprising.
- **`ConnectBookingDetail`** — one appointment, and the two things anyone
  opens it to do. Rescheduling re-reads the slots with `excludeBookingId` set
  to this booking; without that the appointment blocks its own replacement,
  and moving it by half an hour — the only move most people ever make — offers
  nothing.

## Hooks

Every component above has a hook underneath it, and the hook is the escape
hatch when you want the data without the markup: `useConnect`,
`useConnectUser`, `useConnectEntitlements`, `useConnectFeature`,
`useConnectPlans`, `useConnectCheckout`, `useConnectAddonUsage`,
`useConnectApps`, `useConnectWorkspaces`, `useConnectWorkspaceMembers`,
`useConnectWorkspaceType`, `useConnectSessions`, `useConnectIntegrations`,
`useConnectPush`, `useConnectPlatformAccess`, `useConnectCompanyProfile`,
`useConnectAudience`, `useBookableEventTypes`, `useAvailableSlots`,
`useBookings`, `useBookingActions`, `useActivePath`, `useConnectResource`,
`useConnectOptional`, `useConnectShell`, `useConnectAccessGate`.

Signatures and return types are in the
[client reference](/docs/reference/client). Two worth knowing about by name:

- **`useConnectResource`** is the generic one the others are built on — one
  bridge read, shared through the provider's cache. It waits for a session by
  default, since every resource route but one needs it and firing early buys a
  guaranteed 401; `requiresSession: false` opts out, which is what the
  grievance notice on a public page needs.
- **`useConnectOptional`** inherits the context when there is one and does not
  throw when there is not — for a component whose whole purpose is the visitor
  who has no session. It is not a way to make an ordinary component tolerant
  of a missing provider: a session-reading component that silently renders a
  signed-out state because somebody forgot to wrap their app is a bug that
  looks like a product decision.