Guides · September 6, 2023
shadcn/ui for E-Commerce: The Complete Component Guide
shadcn/ui works well for e-commerce because it gives you accessible, unstyled component primitives you own outright, then lets you assemble commerce-specific patterns (variant pickers, cart drawers, filters) on top instead of fighting a themed component library. Here is how to structure a shadcn-based storefront end to end.
By Polo Themes
shadcn/ui is a good fit for e-commerce because it is not really a component library — it is a collection of accessible, unstyled component source files (built on Radix primitives and Tailwind CSS) that you copy into your own codebase and own outright. That ownership model matters more in commerce than almost any other UI domain, because product pages, variant pickers, cart drawers, and checkout flows are exactly the places where a generic themed library's defaults stop matching what your store actually needs. This guide covers how to structure a shadcn-based storefront, which primitives map to which commerce patterns, and where the approach has real limits.
Why shadcn/ui Suits Commerce UI Specifically
Most component libraries ship pre-styled, pre-behaviored components and ask you to override them through props, theme tokens, or CSS-in-JS escape hatches. That works fine for admin dashboards and marketing sites where the components are mostly generic. E-commerce interfaces are different: a product variant selector needs to encode out-of-stock states, swatch previews, and price deltas per option; a cart drawer needs optimistic updates, quantity steppers, and empty-state messaging tuned to your merchandising; a filter sidebar needs faceted counts that update as selections change. These are compositions of simpler primitives, not single drop-in widgets, and no library ships the exact composition your catalog needs.
shadcn/ui sidesteps this by giving you the primitives — dialog, popover, select, checkbox, accordion, command palette, toast — as plain React components living in your own components/ui directory, styled with Tailwind classes you can edit directly. There is no theme object to fight, no CSS specificity war with a vendor stylesheet, and no waiting on a library maintainer to expose the one prop you need. You edit the source. For a storefront where the checkout flow, PDP layout, and cart behavior are core to conversion rate, that level of control is not a nice-to-have; it is close to a requirement once you move past a generic template.
The tradeoff is that shadcn/ui gives you almost nothing commerce-specific out of the box. There is no product card, no variant matrix, no cart component. You are assembling commerce UI from general-purpose primitives (dialog, tabs, popover, radio-group) the same way you would assemble any other interface. That is the right tradeoff if you have design and frontend capacity to do that assembly well; it is the wrong tradeoff if you want a working storefront in an afternoon. Keep that distinction in mind as you plan the build.
The Core Primitives You Will Actually Use
A commerce frontend does not need all of shadcn's roughly fifty components. In practice, a small subset does most of the work, and it is worth knowing which primitive maps to which commerce pattern before you start wiring pages together.
Dialog and Sheet for cart and quick-view
The Sheet component (a slide-in panel built on Radix's Dialog primitive) is the natural base for a cart drawer — it handles focus trapping, escape-to-close, and backdrop click-out for free, so you only need to build the cart line-item list and totals inside it. The same Dialog primitive, in its centered form, works well for a quick-view modal that shows a product's images and variant picker without a full page navigation. Both get accessible keyboard handling and screen-reader announcements without you writing any of that logic yourself, which is easy to get subtly wrong if you build it from scratch.
Radio Group and Toggle Group for variant pickers
Product options — size, color, material — map cleanly onto RadioGroup or ToggleGroup, both of which give you correct ARIA roles and keyboard navigation (arrow keys move between options, matching native radio behavior) with zero extra work. The commerce-specific part you add yourself is disabling combinations that are out of stock, showing a color swatch instead of a label, and syncing the selected combination to the product's price and gallery image. None of that is hard, but it is exactly the layer a generic pre-styled component would make awkward to customize, since it usually was not designed with a swatch-and-availability matrix in mind.
Command for search and quick navigation
The Command component (a filterable command-palette pattern, the same shape as a Spotlight or Linear-style search) is a strong base for storefront search-as-you-type, especially paired with a keyboard shortcut to open it. For catalogs above a few hundred SKUs, wiring this to a real search index rather than client-side filtering matters for both speed and relevance, but the UI shell — the input, the grouped results list, the keyboard navigation between results — is exactly what Command already provides.
Accordion and Tabs for PDP information density
Product description, shipping details, size guide, and reviews compete for space below the fold on a product page. Accordion handles the common pattern of collapsing all of that into expandable sections so the page does not feel overwhelming on first load, while Tabs suits cases where you want the sections visible as a switchable set rather than stacked. Both are simple compositions once the primitive exists — the design decision (which sections default open, whether reviews get their own tab) matters more than the implementation.
Checkbox, Select, and Slider for faceted filtering
Collection and search-results pages typically filter by category, price range, size, and color simultaneously. Checkbox groups handle multi-select facets like size or brand; Slider handles price range; Select suits sort order. The genuinely hard part of faceted filtering is not the UI — it is keeping facet counts accurate as selections change and making sure filter state round-trips through the URL so filtered views are shareable and back-button-safe. shadcn's primitives handle the interaction layer; the state and URL-sync logic is yours to build regardless of which component library you choose.
Toast and Skeleton for feedback and perceived speed
Add-to-cart actions, wishlist toggles, and form submissions all benefit from a toast confirmation rather than a full page reload or silent state change — shoppers need to know the click registered. Skeleton components matter more in commerce than most domains because product images and prices are usually the last thing to arrive from a slower data source (an inventory check, a personalized price), and a layout that shifts as that data resolves reads as broken. Reserving space with skeletons during that fetch keeps the page visually stable.
Building the Product Detail Page
A PDP built from shadcn primitives typically breaks down into: an image gallery (often a custom carousel, since shadcn's carousel wraps Embla and needs commerce-specific configuration for zoom and thumbnails), a variant selector built from RadioGroup/ToggleGroup synced to a small client-side state machine for price and availability, a sticky add-to-cart bar for mobile that stays visible as the shopper scrolls through description content, and an Accordion for supplementary information. None of these are exotic — the value of starting from shadcn primitives is that the accessibility and keyboard-interaction baseline is already correct, so the work left is commerce logic (variant-to-price mapping, inventory checks, gallery-to-variant syncing) rather than reinventing focus management.
Where teams get this wrong is treating the variant picker as a purely visual component and bolting the pricing and availability logic on as an afterthought. Model the variant state machine first — given a partial selection, which remaining options are still available, and what does the resulting SKU cost — then build the UI on top of that model. The RadioGroup/ToggleGroup primitives are just the rendering layer; the state machine is the actual product engineering.
Cart, Checkout, and the Limits of a Component Library
A cart drawer built on Sheet handles the interaction shell well: opening, closing, focus return to the trigger element on close. The commerce-specific work is optimistic quantity updates (update the UI immediately, reconcile with the server response, roll back on failure), empty-cart messaging that nudges toward recovery rather than just showing nothing, and keeping the cart icon's item count in sync across the whole app without a full page reload. shadcn does not help with any of that — it is application state management (React Context, Zustand, or your commerce platform's client SDK), and it would be the same amount of work with any other component library.
Checkout is where it is worth being honest about limits. A composed set of shadcn Input, Select, and RadioGroup primitives can absolutely build a checkout form's shell, but real checkout also has to handle payment tokenization, address validation, tax calculation, and PCI-scope boundaries — none of which a UI primitive library touches. Most teams are better off using their commerce platform's or payment processor's hosted checkout or embedded checkout components for the payment step specifically, and reserving the custom shadcn-built UI for everything around it (cart review, shipping method selection, order summary). Rebuilding raw card-number capture yourself is rarely the right tradeoff even with excellent component primitives underneath it.
Theming a shadcn Storefront Without Losing Consistency
Because shadcn components are Tailwind-styled source files rather than a themed package, consistency comes from disciplined use of CSS variables and a design-token layer, not from a theme prop. The standard shadcn setup already defines semantic color variables (background, foreground, primary, muted, destructive, border) in your global stylesheet, and every generated component consumes those variables rather than hardcoded colors. The discipline worth enforcing on a commerce build specifically is resisting the temptation to hardcode a brand color directly into a single component the first time a design calls for something the token set does not cover — extend the token set instead, the same way you would extend a proper design system, so a rebrand or a dark-mode pass does not turn into a file-by-file hunt.
This is also where working from a well-structured design source pays off before a single component gets built. Teams that start from clean, componentized Figma files — with color styles, type scales, and named variants already defined — end up with a much shorter path to a consistent token layer than teams reverse-engineering tokens from a handful of already-built pages. If you are assembling a commerce design system from scratch, our Figma UI kits are built with that componentized structure in mind, and translate cleanly into a token-driven Tailwind setup even though they were designed for Shopify storefronts rather than a shadcn build specifically — the color, spacing, and typography decisions carry over regardless of the implementation stack.
Performance and Bundle-Size Considerations
Because you copy in only the components you use, a shadcn-based storefront tends to avoid the all-or-nothing bundle cost of importing a full component library, but that benefit only holds if you are disciplined about not copying in components you do not need yet. Each component pulls in its underlying Radix primitive as a dependency, and Radix primitives are individually small, but they add up across a dozen components. Tree-shaking works correctly as long as you are not re-exporting everything from a single barrel file — import each component from its own module path so unused ones are actually dropped from the client bundle. On the commerce side specifically, keep the product gallery, variant picker, and add-to-cart control in the initial client bundle for the PDP, and lazy-load anything below the fold (reviews, related products, an accordion's collapsed content) since those do not need to be interactive before first paint.
Where a Full Theme Still Beats a Component-Library Build
It is worth being direct about when this approach is not the right call. Building a storefront from shadcn primitives means every commerce pattern — variant logic, cart state, filter facets, checkout flow — is assembled and tested by your own team, on your own timeline. That is the right tradeoff for a team with frontend capacity that wants full control over a differentiated storefront experience. It is the wrong tradeoff for a merchant who wants to launch quickly on a proven, complete theme without engineering headcount dedicated to the frontend. If that describes your situation today, a ready-built Shopify theme — like the options in our Shopify theme catalog — gets a full storefront live without any of the component-assembly work this guide describes, and remains the more practical path for most merchants launching now rather than investing in a custom headless build.
The two approaches are not mutually exclusive across a company's lifetime, either. It is common for a store to launch on a complete theme, grow, and only later have the engineering capacity and the specific customization needs that justify a bespoke frontend built from primitives like shadcn's. Treat the component-library route as the right tool once you have outgrown the customization ceiling of a theme, not as a default starting point for every store.
Frequently Asked Questions
Is shadcn/ui a component library or a design system?
Neither, strictly. It is a curated set of accessible component source files you copy into your project and own directly. You still have to build the design system layer — tokens, spacing scale, brand color decisions — around it, which is different from installing a pre-themed library where those decisions are already made for you.
Does shadcn/ui work with Next.js e-commerce builds specifically?
Yes — it is commonly paired with Next.js and Tailwind CSS, and its components are plain React with no framework lock-in beyond that. It works equally well with any React meta-framework; there is nothing Next.js-specific in the primitives themselves.
Can shadcn/ui handle a full checkout flow?
It can build the UI shell around checkout — shipping selection, order summary, form layout — but payment capture itself should generally go through your payment processor's or commerce platform's hosted or embedded checkout component for PCI-compliance and tax-calculation reasons, not a hand-built form on top of raw UI primitives.
How much custom code does a shadcn storefront actually require compared to a theme?
Substantially more. A theme ships working commerce patterns (variant pickers, cart, checkout) out of the box; shadcn ships the accessible building blocks and expects you to assemble those patterns yourself. Budget real frontend engineering time for the assembly, state management, and platform integration — it is a from-scratch build, just one with a strong accessibility and styling foundation underneath it.