Network badges: showing which chain a token lives on
How to design the token icon with a small chain badge, from size ratios and the cutout ring to deciding when a badge is worth showing at all.
6 min read
USDC exists on Ethereum, Base, Arbitrum One, Polygon, Solana and many other chains, and to a user they all look the same: the same logo, the same ticker, often the same price. They are not interchangeable. Sending USDC on the wrong network can mean a deposit an exchange doesn't credit, and bridging the wrong one wastes fees. The common answer in wallet and DeFi interfaces is a network badge: a small chain logo on the lower-right corner of the token icon.
The pattern looks simple, but small details decide whether it reads at 24 px. This guide covers when to show a badge, how big to make it, how to separate it from the token with a cutout ring, and how to build it in CSS and React.
When the badge earns its place
A badge is information, and information competes for attention. Show it when the chain changes what the user can do with the token:
| Screen | Badge? | Reason |
|---|---|---|
| Multichain balance list | Yes | The same token on two chains is two separate balances |
| Send and receive | Yes, plus the chain name in text | A wrong chain is the expensive mistake here |
| Bridge, source and destination | Yes | The whole point of the screen is the chain |
| Token selector in a multichain app | Yes | Several rows can share a ticker |
| Bridged variants, such as USDC.e | Yes | The label alone doesn't say where it came from |
| Single-chain app | No | Every token is on the same chain |
| A list already filtered to one chain | No | The heading says it once |
| Price chart or market data page | Usually no | The price is usually the same across chains |
Native coins on their own chain are the judgment call. A badge on ETH on Ethereum or SOL on Solana repeats what the token logo already says, and many wallets leave it off there. ETH on Base or Arbitrum One is different: the token is the same asset but the chain is not, so it gets a badge. Whichever rule you choose, apply it to every native coin in the product, not case by case.
Size and position
The badge has to be large enough for the chain logo to stay recognizable and small enough that the token remains the main subject. A badge around 35 to 45 percent of the token's diameter works across common sizes:
| Token | Badge | Ring | Overhang |
|---|---|---|---|
| 24 px | 10 px | 1.5 px | 1 px |
| 32 px | 12 to 14 px | 2 px | 2 px |
| 40 px | 16 px | 2 px | 2 px |
| 48 px | 18 to 20 px | 2 to 3 px | 2 px |
| 64 px | 24 px | 3 px | 3 px |
Below a 24 px token the badge falls under 10 px, where most chain logos turn into colored dots. At 16 and 20 px, drop the badge and put the chain name in text next to the token.
Position it at the bottom right, overhanging the token's bounding box by a pixel or two. The overhang matters: a badge kept fully inside the box covers too much of the token, and a badge hanging far outside it collides with the text next to the icon. Keep the same corner everywhere so people learn where to look.
The badge itself needs an edge. Free-standing chain marks have no outline of their own, so at 12 px they blur into the token behind them. A chain logo on its own colored background, clipped to a circle, holds up much better. If the chain logo is dark, the same problems apply as on any dark surface; see when a dark logo needs a light tile.
The cutout ring
Without separation, the badge's edge merges with the token's colors and the two logos read as one blob. The fix is a thin gap around the badge, the cutout ring. There are two ways to draw it.
A border in the background color. Give the badge box-shadow: 0 0 0 2px var(--surface). It's one line and looks right on a plain background. It breaks the moment the row gets a hover or selected state: the ring stays the old background color and appears as a dark halo on a lighter row.
A real hole in the token. Mask a circle out of the token image where the badge sits. The page shows through, whatever color it is, so hover states, gradients and images all work.
.token-with-badge {
--size: 32px;
--badge: 13px;
--ring: 2px;
--overhang: 2px;
--center: calc(var(--size) + var(--overhang) - var(--badge) / 2);
--cut: calc(var(--badge) / 2 + var(--ring));
position: relative;
display: inline-block;
width: var(--size);
height: var(--size);
}
.token-with-badge > .token {
display: block;
width: 100%;
height: 100%;
mask-image: radial-gradient(
circle at var(--center) var(--center),
transparent var(--cut),
#000 calc(var(--cut) + 0.5px)
);
}
.token-with-badge > .badge {
position: absolute;
right: calc(var(--overhang) * -1);
bottom: calc(var(--overhang) * -1);
width: var(--badge);
height: var(--badge);
border-radius: 50%;
}
The half-pixel step between transparent and #000 softens the edge of the hole so it doesn't look jagged. Unprefixed mask-image works in current versions of the major browsers; if you support older engines, add -webkit-mask-image with the same value. The same masking technique separates the two coins in a pool icon, covered in designing token pair icons.
A React component
The component takes the label as a prop because the badge carries meaning, and that meaning needs a text equivalent:
import type { CSSProperties } from 'react'
type TokenWithChainProps = {
tokenSrc: string
chainSrc: string
label: string // for example "USDC on Base"
size?: number
}
export function TokenWithChain({ tokenSrc, chainSrc, label, size = 32 }: TokenWithChainProps) {
const style = {
'--size': `${size}px`,
'--badge': `${Math.round(size * 0.4)}px`,
} as CSSProperties
return (
<span className="token-with-badge" role="img" aria-label={label} style={style}>
<img className="token" src={tokenSrc} alt="" />
<img className="badge" src={chainSrc} alt="" />
</span>
)
}
The wrapper is announced as one image, "USDC on Base", and the two inner images are hidden with empty alt. If the row next to it already shows "USDC" and "Base" as visible text, the whole composite is decorative: drop role and aria-label and keep both alt attributes empty, so screen readers don't hear the same thing twice. The guide on alt text for logos goes through these cases.
Don't let the badge carry the warning alone
A 13 px logo is easy to misread, especially between chains with similar colors. On screens where the chain is a matter of money, such as send, withdraw and bridge, write the network name next to the amount or the address, and show it again in the confirmation step. The badge helps people scan a list; the text is what they check before they sign. Designing for many chains covers the wider flow.
Badge checklist:
- Badge at about 40 percent of the token, bottom right, overhanging by 1 to 3 px.
- A real cutout so hover and selected rows still look clean.
- Chain logos on their own background, clipped to a circle.
- No badge below a 24 px token; use text instead.
- One rule for native coins, applied everywhere.
- The chain name in text wherever a wrong chain costs money.