ethereum/wallet-connect-ux
Wallet Connection UX Best Practices: RainbowKit vs ConnectKit vs AppKit
ethereumguide👥 Communityconfidence highhealth 100%
v1.0.0·Updated 3/20/2026
Comparison of the Three Main Libraries
| Library | Maintainer | Built On | UI Style | Customizability | Recommended Use Case |
|---|---|---|---|---|---|
| RainbowKit | Rainbow | wagmi | Polished, rainbow aesthetic | Medium | General dApps, fast launch |
| ConnectKit | Family | wagmi | Clean and modern | High | Brand consistency focused |
| AppKit (Web3Modal v3) | WalletConnect | wagmi/ethers | Generic | Medium | Multi-chain, multi-ecosystem |
| wagmi native | wagmi | — | No UI | Fully custom | Full customization needed |
RainbowKit (Least Effort)
npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query
// App.tsx
import '@rainbow-me/rainbowkit/styles.css'
import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { mainnet, polygon, base } from 'wagmi/chains'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const config = getDefaultConfig({
appName: 'My App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID', // get from cloud.walletconnect.com
chains: [mainnet, base, polygon],
})
export function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={new QueryClient()}>
<RainbowKitProvider>
<YourApp />
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}
// Usage
import { ConnectButton } from '@rainbow-me/rainbowkit'
<ConnectButton /> // done in one line
Custom ConnectButton:
<ConnectButton.Custom>
{({ account, chain, openConnectModal, openChainModal, mounted }) => (
<button onClick={openConnectModal}>
{account ? account.displayName : 'Connect'}
</button>
)}
</ConnectButton.Custom>
ConnectKit (More Customization)
npm install connectkit wagmi viem @tanstack/react-query
import { ConnectKitProvider, ConnectKitButton, getDefaultConfig } from 'connectkit'
const config = createConfig(getDefaultConfig({
walletConnectProjectId: 'YOUR_PROJECT_ID',
appName: 'My App',
chains: [mainnet, base],
}))
// Themes: auto | light | dark | web95 | retro | soft | midnight | minimal | rounded | nouns
<ConnectKitProvider theme="auto" customTheme={{ "--ck-font-family": "Inter" }}>
<ConnectKitButton />
</ConnectKitProvider>
UX Best Practices
1. Show Correct Chain State
const { chain } = useAccount()
const { chains, switchChain } = useSwitchChain()
// Detect wrong chain and prompt user to switch
if (chain?.id !== expectedChainId) {
return <button onClick={() => switchChain({ chainId: expectedChainId })}>
Switch to {expectedChain.name}
</button>
}
2. ENS Name + Avatar Display
const { data: ensName } = useEnsName({ address, chainId: mainnet.id })
const { data: avatar } = useEnsAvatar({ name: ensName!, chainId: mainnet.id })
// Show: vitalik.eth instead of 0xd8dA...
const displayName = ensName ?? truncateAddress(address) // "0xd8dA...6045"
3. Transaction Status Feedback
const { writeContract, data: hash, isPending } = useWriteContract()
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash })
// Three-phase feedback
if (isPending) return <span>Waiting for wallet confirmation...</span>
if (isConfirming) return <span>Confirming transaction (0/{targetConfirmations})</span>
if (isSuccess) return <span>✅ Transaction successful!</span>
4. Gas Estimation
import { useEstimateGas } from 'wagmi'
const { data: gasEstimate } = useEstimateGas({ to, data, value })
// Display estimated gas cost
5. Mobile Compatibility
- RainbowKit supports WalletConnect QR and mobile deep links out of the box
- Prioritize MetaMask Mobile / Trust Wallet when a mobile device is detected
- iOS Safari note: wallet actions must be triggered by a user gesture (cannot open automatically)
Common Pitfalls
projectIdmust be obtained from cloud.walletconnect.com — do not use the example ID- ENS lookups only work on Ethereum Mainnet (chainId=1); other chains return null
- Do not use wagmi hooks directly in SSR — wrap them inside a client component
Wallet Connection UX Best Practices
Comparison of the Three Main Libraries
| Library | Maintainer | Built On | UI Style | Customizability | Recommended Use Case |
|---|---|---|---|---|---|
| RainbowKit | Rainbow | wagmi | Polished, rainbow aesthetic | Medium | General dApps, fast launch |
| ConnectKit | Family | wagmi | Clean and modern | High | Brand consistency focused |
| AppKit (Web3Modal v3) | WalletConnect | wagmi/ethers | Generic | Medium | Multi-chain, multi-ecosystem |
| wagmi native | wagmi | — | No UI | Fully custom | Full customization needed |
RainbowKit (Least Effort)
npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query
// App.tsx
import '@rainbow-me/rainbowkit/styles.css'
import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { mainnet, polygon, base } from 'wagmi/chains'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const config = getDefaultConfig({
appName: 'My App',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID', // get from cloud.walletconnect.com
chains: [mainnet, base, polygon],
})
export function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={new QueryClient()}>
<RainbowKitProvider>
<YourApp />
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}
// Usage
import { ConnectButton } from '@rainbow-me/rainbowkit'
<ConnectButton /> // done in one line
Custom ConnectButton:
<ConnectButton.Custom>
{({ account, chain, openConnectModal, openChainModal, mounted }) => (
<button onClick={openConnectModal}>
{account ? account.displayName : 'Connect'}
</button>
)}
</ConnectButton.Custom>
ConnectKit (More Customization)
npm install connectkit wagmi viem @tanstack/react-query
import { ConnectKitProvider, ConnectKitButton, getDefaultConfig } from 'connectkit'
const config = createConfig(getDefaultConfig({
walletConnectProjectId: 'YOUR_PROJECT_ID',
appName: 'My App',
chains: [mainnet, base],
}))
// Themes: auto | light | dark | web95 | retro | soft | midnight | minimal | rounded | nouns
<ConnectKitProvider theme="auto" customTheme={{ "--ck-font-family": "Inter" }}>
<ConnectKitButton />
</ConnectKitProvider>
UX Best Practices
1. Show Correct Chain State
const { chain } = useAccount()
const { chains, switchChain } = useSwitchChain()
// Detect wrong chain and prompt user to switch
if (chain?.id !== expectedChainId) {
return <button onClick={() => switchChain({ chainId: expectedChainId })}>
Switch to {expectedChain.name}
</button>
}
2. ENS Name + Avatar Display
const { data: ensName } = useEnsName({ address, chainId: mainnet.id })
const { data: avatar } = useEnsAvatar({ name: ensName!, chainId: mainnet.id })
// Show: vitalik.eth instead of 0xd8dA...
const displayName = ensName ?? truncateAddress(address) // "0xd8dA...6045"
3. Transaction Status Feedback
const { writeContract, data: hash, isPending } = useWriteContract()
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash })
// Three-phase feedback
if (isPending) return <span>Waiting for wallet confirmation...</span>
if (isConfirming) return <span>Confirming transaction (0/{targetConfirmations})</span>
if (isSuccess) return <span>✅ Transaction successful!</span>
4. Gas Estimation
import { useEstimateGas } from 'wagmi'
const { data: gasEstimate } = useEstimateGas({ to, data, value })
// Display estimated gas cost
5. Mobile Compatibility
- RainbowKit supports WalletConnect QR and mobile deep links out of the box
- Prioritize MetaMask Mobile / Trust Wallet when a mobile device is detected
- iOS Safari note: wallet actions must be triggered by a user gesture (cannot open automatically)
Common Pitfalls
projectIdmust be obtained from cloud.walletconnect.com — do not use the example ID- ENS lookups only work on Ethereum Mainnet (chainId=1); other chains return null
- Do not use wagmi hooks directly in SSR — wrap them inside a client component
- Handle chain-switch failures (user rejection) gracefully with a friendly message — don't crash