Skip to content

Use your own icons

Evolve ships with Lucide icons. Your brand probably has its own. You can restyle the icons, swap one where it renders, or replace the whole set, without touching a feature package.

Level You change When Evolve updates
1. Restyle every icon size, color and stroke, with CSS You keep everything.
2. Swap one icon in one place the icon of one component, with a prop You keep everything.
3. Swap icons everywhere one, several or all icons, in every component You keep everything; new roles render the shipped glyph until you map them.

Components never import an icon library. They render the Icon primitive from @evolve-storefront/ui/icon with a role:

<Icon name="cart" />

One map in @evolve-storefront/ui/icons turns each role into a glyph. Roles name what an icon means, not what it looks like: wishlist, not heart. So you remap one entry, and every place that means “wishlist” follows.

Some of the roles:

Role Meaning Shipped glyph
cart the cart ShoppingCart
wishlist a shopper’s saved items Heart
shoppingList a company’s shopping lists ListChecks
search the search entry point Search
account the account area CircleUserRound
close dismiss a dialog or sheet X
expand / collapse open or close a control ChevronDown / ChevronUp
remove delete an item Trash2
success / warning / error / info a status CircleCheck / TriangleAlert / CircleAlert / Info

The map has about 80 roles, each with a comment on when to use it. Open node_modules/@evolve-storefront/ui/src/icons.ts to see them all. The Icon reference has the primitive’s props.

Every rendered icon carries data-component="icon", its role in data-icon, and aria-hidden, because the button or link around it carries the accessible name.

Target data-component="icon" from your global CSS. Add data-icon to restyle one role:

/* src/app/global.css */
[data-component="icon"] {
width: 1.25rem;
height: 1.25rem;
stroke-width: 1.5;
}
[data-component="icon"][data-icon="wishlist"] {
color: var(--color-brand);
}

These rules win over the size-4 the primitive sets, because they sit outside a cascade layer. Customize a component explains why.

Components that show an icon you are likely to change take it as a prop. Pass any ReactNode; it also works from a Server Component.

import { ShoppingListAddButton } from "@evolve-storefront/feature-shopping-list/shopping-list-add-button";
import { BookmarkIcon } from "@/icons/bookmark";
<ShoppingListAddButton sku={product.sku} icon={<BookmarkIcon aria-hidden />} />
Component Prop Default role
ShoppingListAddButton (feature-shopping-list) icon wishlist
Dialog, Sheet (ui) closeIcon close
CarouselPrevious, CarouselNext (ui) icon previous, next
BreadcrumbSeparator (ui) children breadcrumbSeparator

To change a role in every place it shows, go to level 3.

Write your own icon map, then point @evolve-storefront/ui/icons at it. Every Icon reads its glyphs from that subpath, in the features and in the ui components alike, so every icon follows your map.

  1. Write your map. Spread the shipped map from @evolve-storefront/ui/default-icons, then replace the roles you want:

    import { type IconComponent, type IconName, icons as base } from "@evolve-storefront/ui/default-icons";
    import { BrandCart } from "./brand-cart";
    import { Bookmark } from "./bookmark";
    export const icons = {
    ...base,
    cart: BrandCart,
    wishlist: Bookmark,
    } satisfies Record<IconName, IconComponent>;

    Import the shipped map and its types from default-icons, not from icons: once you alias icons, that subpath is this file.

    An icon is any React component that takes SVG props:

    import type { IconComponent } from "@evolve-storefront/ui/default-icons";
    export const BrandCart: IconComponent = ({ size, ...props }) => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} {...props}>
    <path d="M3 4h2l2.4 11.2a2 2 0 0 0 2 1.6h7.7a2 2 0 0 0 2-1.5L21 8H6" />
    </svg>
    );
  2. Point the subpath at it in next.config.ts:

    const nextConfig = {
    turbopack: {
    resolveAlias: {
    "@evolve-storefront/ui/icons": "./src/icons/icons.ts",
    },
    },
    };

Moving from Lucide to another set is the same map, with every role replaced. Keep the spread of the shipped map anyway: a role that a later version adds then still renders, with the shipped glyph, until you map it.

import { type IconComponent, type IconName, icons as base } from "@evolve-storefront/ui/default-icons";
import * as Brand from "@/brand-icons";
export const icons = {
...base,
cart: Brand.Cart,
wishlist: Brand.Heart,
search: Brand.Magnifier,
close: Brand.Cross,
// …every other role
} satisfies Record<IconName, IconComponent>;

Any React icon library works when its components accept SVG props and render an <svg>.

Every role in the map ships to every page, because Icon looks a role up at runtime. Each glyph is a few path commands, so a full set adds little.

Nothing in the components: Icon stays Evolve’s, and only the glyphs are yours. When you upgrade, look in the changelog for new roles; they show the shipped glyph until you map them.

The alias holds for the whole build, so every site in the project gets the same icons. Read Customize a component for how a subpath alias works.