Chain select
Network selector for wallet switching or filtering, with optional all-networks and unsupported-chain states.
ChainSelect lists the chains you pass in and calls onSelect with the selected chain ID. Your app handles wallet connections and network switching.
chain-select.tsxShow code
import { ChainSelect } from "@/components/ui/chain-select"const chains = [ { id: 1, name: "Ethereum" }, { id: 8453, name: "Base" }, { id: 42161, name: "Arbitrum One" }, { id: 10, name: "Optimism" }, { id: 137, name: "Polygon" },]export function ChainSelectDemo() { const [chainId, setChainId] = useState<number | null>(8453) return ( <ChainSelect chains={chains} value={chainId} onSelect={setChainId} /> )}Installation
npx shadcn add @onchain-ui/chain-selectRequirements
ChainSelect uses the shadcn dropdown-menu. The install adds components/ui/dropdown-menu.tsx if it is missing, or uses your existing file. It also installs NetworkLogo for the chain logos.
Usage
import { ChainSelect } from "@/components/ui/chain-select"
<ChainSelect chains={chains} value={chainId} onSelect={setChainId} />Control the selection through value and onSelect. ChainSelect does not store connection state or make network calls.
To use it as a filter, set allLabel and onClear to add an all-networks item. Use null for the unfiltered state:
const [chainId, setChainId] = useState<number | null>(null)
<ChainSelect
chains={chains}
value={chainId}
allLabel="All networks"
menuLabel="Filter by network"
onClear={() => setChainId(null)}
onSelect={setChainId}
/>Wiring with wagmi
Read the available chains with useChains and call useSwitchChain when the user selects one:
"use client"
import { useChains, useConnection, useSwitchChain } from "wagmi"
import { ChainSelect } from "@/components/ui/chain-select"
export function ChainSwitcher() {
const { chainId } = useConnection()
const chains = useChains()
const switchChain = useSwitchChain()
return (
<ChainSelect
chains={chains.map((chain) => ({
id: chain.id,
name: chain.name,
badge: chain.testnet ? "Testnet" : undefined,
}))}
value={chainId ?? null}
pendingChainId={switchChain.isPending ? switchChain.variables?.chainId : null}
onSelect={(id) => {
const next = chains.find((chain) => chain.id === id)
if (next) switchChain.mutate({ chainId: next.id })
}}
/>
)
}onSelect returns a number. If you augment wagmi's Register type, switchChain expects one of your configured chain IDs. The example looks up the selected chain to narrow the type before calling mutate.
The snippet targets wagmi 3. On wagmi 2, read chainId from useAccount() and the chain list from useSwitchChain().chains. Call switchChain({ chainId }) instead of switchChain.mutate(...). ChainSelect itself does not import wagmi.
Unsupported networks
A wallet can connect to a network your app does not support. When value is set to an ID absent from chains, the trigger shows "Unsupported network" with warning styles. The menu still lists your supported chains so the user can switch.
chain-select-unsupported.tsxShow code
import { ChainSelect } from "@/components/ui/chain-select"export function ChainSelectUnsupported() { // The wallet is on a chain this app does not support. const [chainId, setChainId] = useState<number | null>(5315) return ( <ChainSelect chains={chains} value={chainId} onSelect={setChainId} /> )}Pass unsupportedLabel to change the warning text.
Examples
Disconnected
value={null} shows the placeholder and reserves space for the network logo.
chain-select-disconnected.tsxShow code
import { ChainSelect } from "@/components/ui/chain-select"export function ChainSelectDisconnected() { const [chainId, setChainId] = useState<number | null>(null) return ( <ChainSelect chains={chains} value={chainId} onSelect={setChainId} /> )}Switching
pendingChainId marks the chain being switched to and locks the control while the wallet prompt is open.
chain-select-pending.tsxShow code
import { ChainSelect } from "@/components/ui/chain-select"export function ChainSelectPending() { return ( <ChainSelect chains={chains} value={8453} pendingChainId={42161} onSelect={switchChain} /> )}Sizes
Use showLabel={false} for a logo-only trigger in tight headers. The accessible name falls back to the network name.
chain-select-sizes.tsxShow code
import { ChainSelect } from "@/components/ui/chain-select"export function ChainSelectSizes() { return ( <div className="flex items-center gap-3"> <ChainSelect chains={chains} value={chainId} onSelect={setChainId} size="sm" /> <ChainSelect chains={chains} value={chainId} onSelect={setChainId} /> <ChainSelect chains={chains} value={chainId} onSelect={setChainId} showLabel={false} /> </div> )}Props
| Prop | Type | Default | Description |
|---|---|---|---|
chains | ChainSelectOption[] | - | Selectable chains, usually the ones your app is configured for |
value | number | null | - | Currently connected chain id |
onSelect | (chainId: number) => void | - | Called with the chain id the user picked |
onClear | () => void | - | Called when the optional all-networks item is picked |
allLabel | string | - | Adds an item that clears the selected chain |
pendingChainId | number | null | - | Chain being switched to. Blocks further selection |
disabled | boolean | false | Disable the whole control |
placeholder | string | "Select network" | Trigger label when value is null |
unsupportedLabel | string | "Unsupported network" | Trigger label when value is missing from chains |
menuLabel | ReactNode | "Switch network" | Heading above the menu items |
showLabel | boolean | true | Show the network name in the trigger |
size | "sm" | "default" | "default" | Visual size |
align | "start" | "center" | "end" | "start" | Menu alignment against the trigger |
className | string | - | Applied to the trigger |
contentClassName | string | - | Applied to the menu |
ChainSelectOption
| Prop | Type | Default | Description |
|---|---|---|---|
id | number | - | EVM chain id |
name | string | null | Built-in network name | Display name |
logoSrc | string | null | Built-in chain logo | Network image URL override |
symbol | string | null | Built-in network symbol | Short symbol for the logo fallback |
disabled | boolean | false | Render but do not allow selection |
badge | ReactNode | - | Rendered after the name, e.g. a "Testnet" badge |