UI component library
The storefront’s UI lives in the @evolve-storefront/ui package
(frontend/packages/ui/) and is organized into two tiers: a small set
of base primitives and a larger set of commerce-specific components
built on top of them.
Architecture
Section titled “Architecture”Directorypackages/ui/src/
Directorybase/ ~16 primitives (Button, Input, Select, …)
- …
Directorycomponents/ ~30 commerce components (ProductCard, Navigation, …)
- …
Directoryhelpers/ Utility functions (cn, typography)
- …
- global.css Design tokens + Tailwind theme (at package root)
- …
Base primitives
Section titled “Base primitives”These are low-level building blocks with no commerce knowledge. They
wrap Radix UI and React Aria (react-aria-components) for
accessibility and use CVA for variant management.
alert-dialog button checkbox container icon input
label radio radio-group range-slider ribbon select
skeleton switch text-area typography
Commerce components
Section titled “Commerce components”Higher-level components that compose base primitives with business logic. Examples include product cards, cart line items, checkout summaries, and navigation.
Product display: product-card product-carousel product-gallery
product-grid product-header product-list
product-specifications product-stock-info reviews-stars
Checkout & cart: checkout-summary quantity-selector
address-card delivery-methods line-item summary
order-status-indicator shopping-list
Content & data: catalog-card catalog-carousel content-teaser
data-table table markdown-container usp-list
Navigation & layout: accordion breadcrumbs card footer
hero navigation
Modals & feedback: dialog toaster tooltip
Variant selectors: variant-selectors (button, color, and image
selector sub-components)
Component patterns
Section titled “Component patterns”CVA variants
Section titled “CVA variants”Components declare their visual variants with Class Variance Authority:
import { cva } from "class-variance-authority";
export const buttonVariants = cva( "inline-flex cursor-pointer items-center justify-center …", { variants: { variant: { primary: "bg-orange-500 font-bold text-black …", secondary: "bg-black font-bold text-white …", tertiary: "text-gray-600 hover:bg-gray-50 …", link: "text-black hover:text-gray-500 …", outline: "bg-white font-semibold ring-2 ring-black …", }, size: { lg: "gap-lg px-4 py-[10px] text-sm", xl: "gap-sm px-4 py-2.5 text-sm md:px-6 md:py-3 md:text-base", xxl: "gap-sm px-4 py-2.5 text-sm md:px-6 md:py-3 md:text-base", }, }, defaultVariants: { variant: "primary", size: "xxl" }, });Radix UI and React Aria for accessibility
Section titled “Radix UI and React Aria for accessibility”Base components use Radix UI and React Aria Components primitives for
keyboard navigation, focus management, and ARIA attributes. The
asChild pattern (via
Slot.Root) lets you swap the rendered element while keeping
behaviour:
const Component = asChild ? Slot.Root : "button";The cn() helper
Section titled “The cn() helper”All components merge class names with cn(), which combines clsx
(conditional classes) and tailwind-merge (deduplication of Tailwind
utilities):
import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]): string { return twMerge(clsx(inputs));}Theming
Section titled “Theming”Design tokens are defined in two layers in global.css. A base layer
sets raw values as CSS custom properties, and the @theme directive
maps them into Tailwind:
/* Base layer - raw design token values */@layer base { :root { --background: hsl(0 0% 100%); --foreground: hsl(240 10% 3.9%); --primary: hsl(240 5.9% 10%); --destructive: hsl(0 72.22% 50.59%); --radius: 0.5rem; }}
/* Tailwind theme - maps tokens to utility classes */@theme { --color-background: var(--background); --color-foreground: var(--foreground); --color-primary: var(--primary); --color-destructive: var(--destructive); --radius-lg: var(--radius);}Dark mode is enabled through a custom Tailwind variant:
@custom-variant dark (&:is(.dark *));This means dark-mode styles activate when an ancestor has the dark
class, rather than relying on prefers-color-scheme.
The library uses Lucide React for icons. A
custom Icon component renders icons as CSS masks rather than inline
SVGs, reducing DOM size:
<span style={{ "--mask-image": `var(--icon-${icon})` }} className="inline-block size-6 bg-current [mask-image:var(--mask-image)]" aria-hidden="true"/>Storybook
Section titled “Storybook”All components have Storybook stories alongside their source files. A hosted instance is available at ui.evolve.labdigital.nl. The Storybook setup includes:
- Accessibility checks via
@storybook/addon-a11y - Responsive viewport presets (minimal through extra-large)
- Auto-generated documentation from component props
Analytics
Section titled “Analytics”The @evolve-storefront/gtm-ecommerce package provides Google Tag
Manager Enhanced Ecommerce events. Ten standard ecommerce events are
implemented:
| Event | Trigger |
|---|---|
view_item |
Product detail page view |
view_item_list |
Product listing page view |
select_item |
Click on a product card |
add_to_cart |
Add product to cart |
remove_from_cart |
Remove product from cart |
view_cart |
Cart page view |
begin_checkout |
Start checkout |
add_shipping_info |
Select shipping method |
add_payment_info |
Select payment method |
purchase |
Order confirmation |
Each event sends a standard GA4 ecommerce payload with item-level
data (SKU, name, brand, category, price, quantity). The core function
sendGTMEcommerceEvent() pushes events to the dataLayer.

