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

LibraryMaintainerBuilt OnUI StyleCustomizabilityRecommended Use Case
RainbowKitRainbowwagmiPolished, rainbow aestheticMediumGeneral dApps, fast launch
ConnectKitFamilywagmiClean and modernHighBrand consistency focused
AppKit (Web3Modal v3)WalletConnectwagmi/ethersGenericMediumMulti-chain, multi-ecosystem
wagmi nativewagmiNo UIFully customFull 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

  • projectId must 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

LibraryMaintainerBuilt OnUI StyleCustomizabilityRecommended Use Case
RainbowKitRainbowwagmiPolished, rainbow aestheticMediumGeneral dApps, fast launch
ConnectKitFamilywagmiClean and modernHighBrand consistency focused
AppKit (Web3Modal v3)WalletConnectwagmi/ethersGenericMediumMulti-chain, multi-ecosystem
wagmi nativewagmiNo UIFully customFull 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

  • projectId must 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