web3icons

Designing a token selector

How to build a token selector with search by name, ticker and address, popular tokens first, balances, chain badges and safe imports for unknown tokens.

5 min read

The token selector is the small dialog that opens when someone taps the token button in a swap form, a send screen or a bridge. It looks simple, but it has to search thousands of tokens, show what the person already holds, distinguish identical tickers on different chains, and keep people away from scam tokens. A slow or confusing selector turns every swap into a hunt.

This guide breaks the selector into its parts: the row, what appears before typing, search, chain context, imports and keyboard use.

Anatomy of a row

Each row has to answer "is this the token I want, and how much do I have?" at a glance. A layout that works on desktop and mobile:

Part Content Notes
Icon Token logo, about 32px Chain badge in the corner when chains are mixed
Primary text Full name, such as "USD Coin" Truncate with an ellipsis if long
Secondary text Ticker, such as "USDC" Plus "Imported" or a warning label if relevant
Balance Amount held Right-aligned, tabular figures
Value Fiat equivalent Smaller, under the balance

Some selectors put the ticker first because traders search by ticker. Either order works if you stay consistent. What matters more is that both are present, since tickers are ambiguous and names are long. Rows of 56px to 64px leave room for two lines of text and a comfortable tap target on phones. For icon size relative to text, see sizing token icons in lists, tables and inputs.

Before the person types

Many selections are tokens the person already holds or a handful of popular ones. Show those first so many selections need no typing at all:

  1. Common tokens as chips at the top: the network's native token and a few major assets, such as ETH, USDC and USDT on Ethereum. Keep the set small, six to eight at most.
  2. Tokens the person holds, sorted by fiat value, highest first. Hide zero balances from this section.
  3. The rest of the verified list, in a stable order, such as by market cap or alphabetically.

Recently used tokens can replace or join the chips. Don't load balances before showing the list. Render rows immediately and fill in balances as they arrive, with a small placeholder in the balance column, so the dialog never opens to a spinner.

Search by name, ticker and address

People type tickers ("usdc"), names ("usd coin"), partial words ("arb") and paste contract addresses. Handle all of them, ignore case, and rank exact matches above partial ones:

type Token = { chainId: number; address: string; symbol: string; name: string }

function score(token: Token, query: string): number {
  const symbol = token.symbol.toLowerCase()
  const name = token.name.toLowerCase()
  if (symbol === query) return 0
  if (name === query) return 1
  if (symbol.startsWith(query)) return 2
  if (name.startsWith(query)) return 3
  if (symbol.includes(query) || name.includes(query)) return 4
  return -1
}

function searchTokens(tokens: Token[], input: string): Token[] {
  const query = input.trim().toLowerCase()
  if (!query) return tokens
  if (/^0x[0-9a-f]{40}$/.test(query)) {
    return tokens.filter((t) => t.address.toLowerCase() === query)
  }
  return tokens
    .map((token) => ({ token, rank: score(token, query) }))
    .filter((r) => r.rank >= 0)
    .sort((a, b) => a.rank - b.rank)
    .map((r) => r.token)
}

Because JavaScript's sort is stable, tokens with the same rank keep their original order, so put held and popular tokens first in the input array and they'll stay first within each rank. Searching a few thousand tokens this way is fast enough to run on every keystroke. Client-side search for icon libraries goes further into ranking, aliases and debouncing.

If an address search finds nothing in the list, that's the cue for the import flow below.

Chain context and badges

In a single-chain app, the chain is implied, and badges add noise. In a multichain selector, the same ticker can appear once per chain, and the chain is part of the token's identity.

Two patterns work:

  • A chain filter at the top of the dialog, as a row of chain logos with names, with "All networks" as one option. Selecting a chain filters the list.
  • A chain badge on each token icon, a small chain logo in the bottom-right corner, when the list shows tokens from several chains at once.

Use both when the list is long. Don't rely on the badge alone to distinguish chains; add the chain name in the secondary text ("USDC · Base") so it can be read, not just recognized. The badge itself is covered in network badges on token icons.

Importing unknown tokens

When someone pastes an address that isn't on your list, read name, symbol and decimals from the contract and show a preview row. Don't add it straight away. Show:

  • The token as the contract reports it, with a neutral fallback icon, not a logo found elsewhere.
  • The full address, the chain and a link to the block explorer.
  • A warning that anyone can create a token with any name, and that this one isn't on a list you trust.
  • A confirmation button labeled for what it does: "Import token".

After import, store it locally for that person, show it with an "Imported" label in every row where it appears, and keep the warning style on its icon. If its ticker matches a verified token, say that explicitly. Why logos can't be trusted for unknown tokens is the subject of fake tokens and spoofed logos.

Keyboard and screen readers

The selector is a dialog containing a search field and a list of options. The ARIA Authoring Practices describe the dialog pattern and the combobox and listbox patterns this maps to. In practice:

  • Focus the search field when the dialog opens.
  • Up and Down arrows move through results while focus stays in the field, with the active row highlighted and exposed through aria-activedescendant.
  • Enter selects the active row, Escape closes the dialog and returns focus to the button that opened it.
  • Each row's accessible name includes the name, ticker, chain and balance, such as "USD Coin, USDC, on Base, balance 1,250.50".

Announce the number of results after typing settles, through a polite status region, so screen reader users know whether their search found anything.

A selector that does these things well disappears into the flow: people open it, pick a token in a second or two, and go back to what they were doing.

More in Web3 UX

All guides