# UI Styling

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](https://tailwindcss.com/) (v4) for utility-first styling and [shadcn/ui](https://ui.shadcn.com/) patterns, and [Radix UI](https://www.radix-ui.com/) 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')`.
- Follow **mobile-first** responsive patterns using breakpoint prefixes: `sm:`, `md:`, `lg:`, `xl:`, `2xl:`.
- **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:

- Backgrounds: `bg-background`, `bg-muted`, `bg-card`
- Text: `text-foreground`, `text-muted-foreground`, `text-primary`
- Borders: `border-border`
- Interactive: `bg-primary`, `text-primary-foreground`, `hover:bg-primary/90`

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/`.

```css
@import "tailwindcss";
@import "tw-animate-css";

@theme inline {
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  /* ... more semantic tokens */
}

:root {
  --radius: 0.625rem;
  --background: #ffffff;
  --foreground: #3f3f46;
  --primary: #2563eb;
  --primary-foreground: #ffffff;
  --secondary: #f3f4f6;
  --muted: #f9fafb;
  --accent: #dbeafe;
  --destructive: #b91c1c;
  --border: #fafafa;
  --ring: #3b82f6;
  /* ... more tokens */
}
```

## shadcn/ui

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:

```bash
npx 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.

```ts
// src/lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));
}
```

## 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.

```ts
// src/components/ui/button.tsx (simplified example)
import { cn } from "@/lib/utils";

type ButtonVariant = "default" | "outline";
type ButtonSize = "default" | "sm";

const variantClasses: Record<ButtonVariant, string> = {
  default: "bg-primary text-primary-foreground hover:bg-primary/90",
  outline: "border bg-background hover:bg-accent hover:text-accent-foreground",
};

const sizeClasses: Record<ButtonSize, string> = {
  default: "h-9 px-4 py-2",
  sm: "h-8 px-3 text-xs",
};

function Button({
  variant = "default",
  size = "default",
  className,
  ...props
}: React.ComponentProps<"button"> & {
  variant?: ButtonVariant;
  size?: ButtonSize;
}) {
  return (
    <button
      className={cn(
        "inline-flex items-center justify-center rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50",
        variantClasses[variant],
        sizeClasses[size],
        className,
      )}
      {...props}
    />
  );
}
```

### Compound Components

Multi-part components are organized as separate functions:

```ts
// src/components/ui/card.tsx
function Card({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      className={cn(
        "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
        className,
      )}
      {...props}
    />
  );
}

function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      className={cn(
        "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
        className,
      )}
      {...props}
    />
  );
}
```

### Direct Utility Class Usage

Feature components apply Tailwind classes directly.

```tsx
<Card
  className={cn(
    "group border rounded-xl overflow-hidden w-full min-w-0 max-w-full",
    "flex flex-col-reverse h-full shadow-sm gap-0 py-0",
    "transition-all duration-200 hover:shadow-md",
    className,
  )}
>
  {/* ... */}
</Card>
```

## Responsive Design

Tailwind uses prefix-based responsive modifiers.

```tsx
<div className="px-4 sm:px-6 lg:px-8 py-8">
  <div className="grid md:grid-cols-2 gap-6">{/* Content */}</div>
  <div className="flex flex-col sm:flex-row gap-4 justify-center">{/* Buttons */}</div>
</div>
```

## Color Utility Enforcement

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.

```css
/* src/theme/tokens/core.css */
--icon-star: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M11.525%202.295a.53.53%200%200%201%20.95%200l2.31%204.679a2.123%202.123%200%200%200%201.595%201.16l5.166.756a.53.53%200%200%201%20.294.904l-3.736%203.638a2.123%202.123%200%200%200-.611%201.878l.882%205.14a.53.53%200%200%201-.771.56l-4.618-2.428a2.122%202.122%200%200%200-1.973%200L6.28%2021.28a.53.53%200%200%201-.77-.56l.881-5.139a2.122%202.122%200%200%200-.611-1.879L2.045%209.865a.53.53%200%200%201%20.294-.904l5.166-.755a2.122%202.122%200%200%200%201.597-1.16z%22%2F%3E%3C%2Fsvg%3E");
--icon-check: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M20%206%209%2017l-5-5%22%2F%3E%3C%2Fsvg%3E");
```

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.

```css
/* src/theme/base.css */
[data-slot="section-title"] {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

[data-slot="section-title"]::before {
  content: "";
  display: inline-block;
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
  background-color: currentColor;
  -webkit-mask: var(--icon-star) center / contain no-repeat;
  mask: var(--icon-star) center / contain no-repeat;
}
```

**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.

```css
[data-slot="section-title"][data-complete]::before {
  -webkit-mask: var(--icon-check) center / contain no-repeat;
  mask: var(--icon-check) center / contain no-repeat;
}
```

:::note
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.
:::

## 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](https://tailwindcss.com/docs/styling-with-utility-classes#managing-duplication) — 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.

```css
/* src/theme/base.css — CSS component class */
@layer components {
  .section-container {
    @apply px-4 sm:px-8 lg:px-16 max-w-screen-2xl mx-auto;
  }
}
```

```tsx
/* React component — has structure, props, and children */
function CategoryBanner({ title, image }: CategoryBannerProps) {
  return (
    <div className="section-container">
      <img src={image} alt="" />
      <h1>{title}</h1>
    </div>
  );
}
```

:::note
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.
:::

## Summary

Quick reference:

| Do                                                       | Don't                                                           |
| -------------------------------------------------------- | --------------------------------------------------------------- |
| Tailwind utility classes                                 | Inline styles, CSS modules, component-level `.css` files        |
| `cn()` for conditional classes                           | Manual string concatenation for `className`                     |
| Design tokens (`bg-background`, `text-muted-foreground`) | Hard-coded colors                                               |
| `npx shadcn@latest add <name>`                           | Manually copying or creating components in `src/components/ui/` |
| Global/theme styles in `src/theme/`                      | Scattered or duplicate global CSS                               |
