UI styling shapes the storefront’s look and feel, including layout, spacing, colors, typography, and interactive states. It provides a consistent, accessible, and responsive shopping experience for your storefront across devices.
Technology Stack
Storefront Next uses Tailwind CSS (v4) for utility-first styling and shadcn/ui patterns, and Radix UI for headless accessible components.
Here are the Tailwind-related dependencies in this package.
Verify dependency versions against the template package.json before each release to ensure version numbers are current.
Dependency
Version
Purpose
tailwindcss
4.x.x+
Utility-first CSS framework
@radix-ui/*
Various
Headless UI primitives
class-variance-authority
0.7.1
Component variant management
clsx
2.1.1
Conditional class composition
tailwind-merge
3.4.0
Tailwind class conflict resolution
Tailwind CSS Rules
Use Tailwind utility classes in component JSX for layout, spacing, typography, and colors.
Use the cn() utility for conditional or combined class names: import { cn } from '@/lib/utils'. Example: cn('rounded p-4', isActive && 'ring-2').
Do not use inline styles (style={{ ... }}) for styling.
Do not use CSS modules (.module.css) or separate CSS files for component-level styles.
Global and theme styles belong in src/theme/ only. The entry point is src/theme/index.css, with tokens split across src/theme/tokens/, base resets in src/theme/base.css, and component overrides in src/theme/overrides/.
Design Tokens
Colors and theme values are defined as CSS variables (design tokens). Use semantic token-based classes instead of hard-coded colors:
Avoid raw color utilities (e.g. bg-[#hex]) so the app stays consistent with the theme.
Global Styles and CSS Variables
The global and theme styles are in src/theme/ only. The entry point is src/theme/index.css, with tokens split across src/theme/tokens/, base resets in src/theme/base.css, and component overrides in src/theme/overrides/.
Presentational UI components are built on Radix UI primitives with shadcn/ui as the styling layer. They live in src/components/ui/.
Adding Components
Add new components only via the official CLI so they are ejected with the correct config and Tailwind setup:
1npx shadcn@latest add<component-name>
This ejects the component into src/components/ui/ with the right dependencies and styles.
Rules
Do add and customize shadcn components by editing the files in src/components/ui/.
Do not create custom components inside src/components/ui/; keep that directory for ejected shadcn components only.
Do not manually copy components from the shadcn docs; always use the CLI so configuration (e.g. components.json) stays in sync.
Keeping src/components/ui/ limited to ejected shadcn components makes upgrades and maintenance predictable. For custom UI, use src/components/ (or another feature directory) and compose or wrap shadcn components as needed.
UI Component Architecture
Components are stored in src/components/ui/ and follow the shadcn/ui pattern.
Built on Radix UI primitives for accessibility.
Styled with Tailwind utility classes.
Variants managed with class-variance-authority (cva).
Classes composed using cn() utility (clsx + tailwind-merge).
The cn() Utility
A helper function that combines clsx for conditional classes with tailwind-merge to resolve conflicting utilities.
1// src/lib/utils.ts2import{clsx, type ClassValue}from 'clsx';3import{twMerge}from 'tailwind-merge';45export function cn(...inputs: ClassValue[]){6 return twMerge(clsx(inputs));7}
Component Styling Patterns
Style patterns for components include variant-based components with CVA, compound components, and direct utility class usage.
Variant-Based Components with CVA
With CVA, you can create variants of an existing component by extending its Tailwind classes without overriding them. Here’s a minimal example.
Hardcoded Tailwind color utilities like bg-red and text-green are blocked via an ESLint rule. Use semantic tokens (for example, bg-primary, text-foreground) or CSS variable classes instead.
Component Library and Icons
Radix UI: Use Radix primitives for accessible behavior (focus, keyboard, ARIA).
Icons: Use Lucide React and React Simple Icons for iconography.
CSS-Only Decorative Icons
When you want a purely decorative icon in front of (or after) an element whose component you shouldn’t fork — a shared title, a label rendered deep in a shadcn primitive — add it with a ::before or ::after pseudo-element in theme CSS instead of editing the JSX. This keeps component-level styling out of components and lets you hook a stable data-slot or structural selector rather than threading a prop through.
The tokens, selectors, and icons below are examples only. Adapt them to your own storefront. The file paths (src/theme/tokens/core.css, and src/theme/base.css) are where global tokens and base rules live.
Use mask + background-color, not content: url(...). A masked SVG is tintable: background-color: currentColor paints the icon in the element’s text color, so it tracks light/dark and theme changes automatically. A content: url(...) image renders at its baked-in colors and can’t inherit currentColor.
Define the SVG once as a token so it’s reusable and themeable. Percent-encode the SVG so characters like #, %, <, >, and quotes survive the data URI intact — an unencoded # or % truncates the URI and the mask silently fails to load. Inside a mask, the SVG’s alpha channel is what matters; the stroke color is never painted, so use a literal black as the stroke value (not currentColor, which doesn’t resolve inside a mask). The visible color comes from background-color on the pseudo-element.
Apply the icon using ::before or ::after in src/theme/base.css. Set content: "" to activate the pseudo-element, then apply the mask and let background-color: currentColor tint it to match the surrounding text color.
Swap icons on state by toggling only the mask. Have the component set a data-* attribute on an ancestor when state changes, then add a rule that overrides just the mask image — the size and tint stay in place.
Keep the decorated structure stable. Decoration hooked to structural selectors (data-slot, :first-child, > span) breaks if that structure shifts. Keep anchored elements mounted — render them empty rather than conditionally removing them — so the icon doesn’t detach. If you decorate by position, don’t conditionally add or remove the siblings around the decorated element.
Note
Accessibility and Design System
Use semantic HTML (<button>, <nav>, <main>, etc.) and appropriate ARIA where needed.
Ensure keyboard navigation and visible focus states for interactive elements.
Aim for WCAG compliance (contrast, focus order, labels).
Keep spacing and typography consistent with the design system defined in src/theme/ and Tailwind config.
Reusing Styles: When to Extract
Tailwind’s utility-first approach means most styling lives inline in JSX. Before extracting a reusable abstraction, read the official guide on managing reuse — it covers multi-cursor editing, loops, and component extraction as the preferred strategies before reaching for CSS abstractions.
Use a React component (the default choice) when:
The pattern involves markup structure—multiple elements, slots, children
There is logic, state, or event handling
It accepts props that change behavior or content
It composes other components (shadcn, Radix, etc.)
Use a CSS component class (@layer components in src/theme/base.css) only when:
The pattern is pure layout/styling—padding, max-width, centering, typography presets
There is no logic, state, or props—just a bag of CSS properties
It needs to be applied to many different HTML elements across the codebase (divs, sections, wrappers)
Utilities need to override it in specific contexts (the components layer is lower specificity than utilities)
Example: section-container—consolidates px-4 sm:px-8 lg:px-16 max-w-screen-2xl mx-auto into one class, used by 30+ files. A page can add max-w-4xl alongside it and the utility wins.
Rule of thumb: if you can express it as a single className string with no JSX children, it’s a CSS class. If it renders elements or accepts props, it’s a React component.
1/* React component — has structure, props, and children */2function CategoryBanner({ title, image }: CategoryBannerProps) {3 return (4 <div className="section-container">5 <img src={image} alt="" />6 <h1>{title}</h1>7 </div>8 );9}
Don’t use @utility for multi-property compositions that need to be overridable. The utility layer has the highest specificity, so any override attempt (e.g., adding max-w-4xl alongside a @utility class) would lose. Use @layer components instead.