diff --git a/appkit/react/early-access/headless.mdx b/appkit/react/early-access/headless.mdx new file mode 100644 index 000000000..2a478b172 --- /dev/null +++ b/appkit/react/early-access/headless.mdx @@ -0,0 +1,404 @@ +--- +title: Headless +'og:image': '/images/headless.png' +--- + + + Descriptive alt text + + + + 💡 Headless is in early-access at the moment and available to limited users. [Contact + us](mailto:sales@reown.com) to get access. + + +AppKit Headless allows developers to build a fully customizable “Connect” experience powered by AppKit’s existing features without using AppKit modal. Headless exposes wallet discovery, rendering WalletConnect QR, and social providers flow via simple hooks and utility functions. + +## Installation + +Headless requires AppKit to be configured with `enableHeadless` flag. See the [installation](../core/installation) docs before getting started and make sure you're enabling headless as expected. + +```tsx +createAppKit({ + ... + enableHeadless: true +}) +``` + +## useAppKitWallets + +The essential hook for accessing all type wallets and methods to build your custom connection user interface. + + + +```tsx useAppKitWallets.tsx +const { data, isFetchingWallets, isFetchingWcUri, wcUri, page, count, fetchWallets, connect } = + useAppKitWallets() +``` + + + +### Returns + + + List of wallets for the initial connect view including WalletConnect wallet and injected wallets + together. If user doesn't have any injected wallets, it'll fill the list with most ranked + WalletConnect wallets. + + + + List of WalletConnect wallets from Wallet Guide API. Useful to display all available WalletConnect + wallets in a separate Search Wallets view. + + + + Boolean that indicates if WalletConnect wallets are being fetched. + + + + Boolean that indicates if a WalletConnect URI is being fetched. + + + + Boolean that indicates if the AppKit is initialized. It's useful to render a fallback UI when the + AppKit initializes and detects all injected wallets. + + + + The current WalletConnect URI for QR code display. This is set when connecting to a WalletConnect + wallet. Reset with resetWcUri(). + + + + The wallet currently being connected to. This is set when a connection is initiated and cleared + when it completes or fails. For WalletConnect wallets, resetWcUri() should be called to clear the + state. + + + + The current page number of WalletConnect wallets. + + + + The total number of available WalletConnect wallets based on the AppKit configurations and given + parameters. + + + + Function to fetch WalletConnect wallets from the explorer API. Allows to list, search and paginate + through the wallets. + + + + Function to connect to a wallet. For WalletConnect wallets: initiates WC connection and returns + the URI with the `wcUri` state. For injected connectors: triggers the extension/wallet directly. + + + + Function to reset the WC URI. Useful to keep `connectingWallet` state sync with the WC URI. Can be + called when the QR code is closed. + + +# Examples + +## Build a Connect UI + + + Descriptive alt text + + +The `useAppKitWallets` hook returns list of injected and WalletConnect wallets let you render directly in the connect page. + +If you configure your AppKit as multi-chain, we suggest rendering a namespace selection component where users should select which namespace to connect when using browser extensions. This could be achieved with the `connectors` array of the `WalletItem`. + +If user select a WalletConnect wallet, you can show the QR code to the users and let them scan it with their preferred wallets. To handle the QR flows, you can use the `isFetchingWcUri`, `wcUri`, and `resetWcUri` props from the `useAppKitWallets` hook. + + + +```tsx ConnectView.tsx +import React, { useState } from 'react' +import type { WalletItem } from '@reown/appkit' +import type { ChainNamespace } from '@reown/appkit/networks' +import { useAppKitWallets } from '@reown/appkit/react' +import { NamespaceSelectionDialog } from './NamespaceDialog' +import { WalletConnectQRDialog } from './WalletConnectQRDialog' + +function ConnectView() { + const { wallets, connect } = useAppKitWallets() + const [selectedWallet, setSelectedWallet] = useState(null) + + async function handleConnect(wallet: WalletItem, namespace?: ChainNamespace) { + await connect(wallet, namespace) + .then(() => { + toast({ title: 'Connected', status: 'success' }) + }) + .catch(() => { + toast({ title: 'Connection declined', status: 'error' }) + }) + } + + return ( + <> + {wallets.map(wallet => ( + { + if (wallet.connectors.length > 1) { + setSelectedWallet(wallet) + } else { + handleConnect(wallet, wallet.connectors[0]?.chain) + } + }} + /> + ))} + setSelectedWallet(null)} + /> + + + ) +} +``` + +```tsx WalletListItem.tsx +export function WalletListItem({ wallet, onConnect }) { + const { connectingWallet } = useAppKitWallets() + + return ( + + ) +} +``` + +```tsx NamespaceDialog.tsx +import { ChevronRightIcon } from 'lucide-react' +import Image from 'next/image' + +import type { WalletItem } from '@reown/appkit' +import type { ChainNamespace } from '@reown/appkit/networks' + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog' +import { Item, ItemActions, ItemContent, ItemMedia, ItemTitle } from '@/components/ui/item' + +interface NamespaceSelectionDialogProps { + wallet: WalletItem | null + onSelect?: (wallet: WalletItem, namespace: ChainNamespace) => void + onClose?: () => void +} + +const CHAIN_NAME_MAP: Record = { + eip155: 'EVM Networks', + solana: 'Solana', + polkadot: 'Polkadot', + bip122: 'Bitcoin', + cosmos: 'Cosmos', + sui: 'Sui', + stacks: 'Stacks', + ton: 'TON' +} + +export function NamespaceSelectionDialog({ + wallet, + onSelect, + onClose +}: NamespaceSelectionDialogProps) { + if (!wallet) return null + + return ( + !open && onClose?.()}> + + + Connect to {wallet.name} + + {wallet.name} supports multiple chains. Select a chain you want to connect to. + + +
+ {wallet.connectors.map(connector => ( + onSelect?.(wallet, connector.chain)}> + {connector.chainImageUrl && ( + + {connector.chain} + + )} + + {CHAIN_NAME_MAP[connector.chain] || connector.chain} + + + + + + ))} +
+
+
+ ) +} +``` + +```tsx WalletConnectQRDialog.tsx +import QRCode from 'react-qr-code' +import Image from 'next/image' +import { Loader2Icon } from 'lucide-react' + +import { useAppKitWallets } from '@reown/appkit/react' + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog' + +export function WalletConnectQRDialog() { + const { wcUri, connectingWallet, isFetchingWcUri, resetWcUri } = useAppKitWallets() + + if (!connectingWallet || connectingWallet.isInjected) { + return null + } + + return ( + { + if (!open) { + resetWcUri() + } + }} + > + + +
+ {connectingWallet.name} + Connect to {connectingWallet.name} +
+ + Scan the QR code with {connectingWallet.name} to connect to your app. + +
+
+ {isFetchingWcUri ? ( + + ) : ( + + )} +
+
+
+ ) +} +``` + +
+ +## Search and Connect to WalletConnect Wallets + + + Descriptive alt text + + +WalletConnect supports 500+ wallets. You can let users to search their own wallets to connect to your app. + + + +```tsx AllWalletsView.tsx +import React, { useState, useEffect } from 'react' +import { useAppKitWallets } from '@reown/appkit/react' + +function AllWalletsView() { + const { wcWallets, isFetchingWcUri, isFetchingWallets, fetchWallets, connect, page } = + useAppKitWallets() + const [inputValue, setInputValue] = useState('') + const searchQuery = useDebounceValue(inputValue, 500) + + useEffect(() => { + fetchWallets?.() + }, []) + + useEffect(() => { + if (searchQuery.length > 0) { + fetchWallets?.({ query: searchQuery }) + } else { + fetchWallets?.() + } + }, [searchQuery]) + + function handleLoadMore() { + if (searchQuery.length > 0) { + fetchWallets?.({ page: page + 1, query: searchQuery }) + } else { + fetchWallets?.({ page: page + 1 }) + } + } + + return ( + <> + setInputValue(e.target.value)} + /> + {isFetchingWallets &&
Loading wallets...
} + {wcWallets.map(wallet => { + return + })} + + + ) +} +``` + +
+ +## Demo + + + + You could check our demo app to play around with example Headless implementation + + + + See the source code of the example Headless implementation. + + diff --git a/docs.json b/docs.json index 3a040c74d..88cb2df47 100644 --- a/docs.json +++ b/docs.json @@ -372,7 +372,8 @@ "group": "Early Access", "pages": [ "appkit/react/early-access/smart-session", - "appkit/react/early-access/chain-abstraction" + "appkit/react/early-access/chain-abstraction", + "appkit/react/early-access/headless" ] }, { diff --git a/images/headless-connect.png b/images/headless-connect.png new file mode 100644 index 000000000..c909818a8 Binary files /dev/null and b/images/headless-connect.png differ diff --git a/images/headless-wc.png b/images/headless-wc.png new file mode 100644 index 000000000..b2ed09c32 Binary files /dev/null and b/images/headless-wc.png differ diff --git a/images/headless.png b/images/headless.png new file mode 100644 index 000000000..3d3d64ae5 Binary files /dev/null and b/images/headless.png differ