Skip to main content

Themes

Themes in EverShop control the visual presentation and user interface of your store. They contain React components that render the pages your customers see, while keeping business logic separate in extensions.

What are Themes?

Themes provide:
  • Visual design and styling with Tailwind CSS
  • React components for pages and UI elements
  • Responsive layouts for mobile and desktop
  • Brand customization (colors, fonts, logos)
  • User experience enhancements
Extensions vs Themes: Extensions handle business logic (APIs, GraphQL, cron jobs), while themes handle presentation (React components, styling). This separation allows you to change your store’s appearance without affecting functionality.

Directory Structure

Themes live in the themes/ directory:
themes/[theme-name]/
├── src/
│   └── pages/           # Page components organized by area
│       ├── all/         # Components for all pages (Header, Footer)
│       ├── homepage/    # Homepage-specific components
│       ├── account/     # Account pages (Login, Register, Dashboard)
│       └── [area]/      # Other page areas
├── dist/                # Compiled files (auto-generated)
├── package.json
└── tsconfig.json

Activating a Theme

Set your active theme in config/default.json:
config/default.json
{
  "system": {
    "theme": "anasuplements"
  }
}
You can only have one active theme at a time. The theme name must match a directory in themes/.

Creating a Theme

1
Create the directory structure
2
mkdir -p themes/myTheme/src/pages
3
Add package.json
4
{
  "name": "myTheme",
  "version": "1.0.0",
  "private": true,
  "main": "src/index.ts",
  "scripts": {
    "build": "evershop build:theme"
  },
  "devDependencies": {
    "@types/react": "^19.0.0"
  }
}
5
Configure TypeScript
6
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "jsx": "react"
  },
  "include": ["src/**/*"]
}
7
Activate the theme
8
Update config/default.json:
9
{
  "system": {
    "theme": "myTheme"
  }
}

Page Components

Page components are React components with special exports that tell EverShop where and when to render them.

Basic Component Structure

Every theme component needs:
  1. A default export (the React component)
  2. A layout export (defines placement and order)
  3. Optional query export (for GraphQL data fetching)
themes/anasuplements/src/pages/homepage/Hero.tsx
import React from 'react';

export default function Hero() {
  return (
    <section className="bg-[#F8FAF9] py-16">
      <div className="container mx-auto px-4">
        <div className="text-center">
          <h1 className="text-4xl md:text-5xl font-bold text-[#2D5A3D] mb-4">
            Bienvenido a Ana's Suplements
          </h1>
          <p className="text-lg text-[#4A5568] mb-8 max-w-2xl mx-auto">
            Los mejores suplementos para tu salud y bienestar.
          </p>
          <div className="flex justify-center gap-4">
            <a 
              href="/catalog" 
              className="bg-[#2D5A3D] text-white px-8 py-3 rounded-lg hover:bg-[#1E3D2A] transition-colors font-semibold"
            >
              Ver Catálogo
            </a>
            <a 
              href="/contact" 
              className="border-2 border-[#2D5A3D] text-[#2D5A3D] px-8 py-3 rounded-lg hover:bg-[#2D5A3D] hover:text-white transition-colors font-semibold"
            >
              Contactar
            </a>
          </div>
        </div>
      </div>
    </section>
  );
}

export const layout = {
  areaId: 'content',
  sortOrder: 1
};

Layout Export

The layout export determines where the component appears:
export const layout = {
  areaId: 'content',     // Which area to render in
  sortOrder: 10          // Rendering order (lower numbers render first)
};
Common areaId values:
  • header - Top of all pages
  • footer - Bottom of all pages
  • content - Main content area
  • productPageBottom - Below product details
  • checkoutPaymentMethod - Payment method selection area

GraphQL Data Fetching

Use the query export to fetch data:
themes/anasuplements/src/pages/all/Header.tsx
import React from 'react';

type HeaderProps = {
  storeName?: string;
  categories?: {
    name: string;
    url: string;
  }[];
};

export default function Header({ storeName = "Ana's Suplements", categories = [] }: HeaderProps) {
  return (
    <header className="bg-[#F8FAF9] border-b border-[#E8F5E9]">
      <div className="container mx-auto px-4 py-4">
        <div className="flex items-center justify-between">
          <a href="/" className="text-2xl font-bold text-[#2D5A3D]">
            {storeName}
          </a>
          
          <nav className="hidden md:flex space-x-6">
            {categories.map((category, index) => (
              <a 
                key={index} 
                href={category.url}
                className="text-[#4A5568] hover:text-[#2D5A3D] transition-colors"
              >
                {category.name}
              </a>
            ))}
          </nav>

          <div className="flex items-center space-x-4">
            <a href="/cart" className="text-[#2D5A3D] hover:text-[#1E3D2A]">
              {/* Cart icon SVG */}
            </a>
            <a href="/account" className="text-[#2D5A3D] hover:text-[#1E3D2A]">
              {/* Account icon SVG */}
            </a>
          </div>
        </div>
      </div>
    </header>
  );
}

export const layout = {
  areaId: 'header',
  sortOrder: 1
};

export const query = `
  query Query {
    storeName: setting(key: "storeName")
    categories {
      name
      url
    }
  }
`;

Page Areas

Organize components by the pages they appear on:

Global Components (all/)

Components that appear on every page:
src/pages/all/
├── Header.tsx
└── Footer.tsx

Homepage Components (homepage/)

Components specific to the homepage:
themes/anasuplements/src/pages/homepage/FeaturedProducts.tsx
import React from 'react';

type ProductCardProps = {
  product: {
    id: string;
    name: string;
    price: number;
    image?: string;
  };
};

function ProductCard({ product }: ProductCardProps) {
  return (
    <div className="bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow border border-[#E8F5E9] overflow-hidden">
      <div className="aspect-square bg-[#F8FAF9] flex items-center justify-center">
        {product.image ? (
          <img src={product.image} alt={product.name} className="w-full h-full object-cover" />
        ) : (
          <svg className="w-16 h-16 text-[#A0C4B0]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
          </svg>
        )}
      </div>
      <div className="p-4">
        <h3 className="font-semibold text-[#2D5A3D] mb-2 line-clamp-2">
          {product.name}
        </h3>
        <div className="flex items-center justify-between">
          <span className="text-lg font-bold text-[#2D5A3D]">
            ${product.price?.toFixed(2)}
          </span>
          <button className="bg-[#2D5A3D] text-white px-4 py-2 rounded text-sm hover:bg-[#1E3D2A] transition-colors">
            Agregar
          </button>
        </div>
      </div>
    </div>
  );
}

export default function FeaturedProducts() {
  const products = [
    { id: '1', name: 'Vitamina D3', price: 15.99 },
    { id: '2', name: 'Omega-3', price: 24.99 },
    { id: '3', name: 'Proteína Whey', price: 45.99 },
    { id: '4', name: 'Magnesio', price: 18.99 },
  ];

  return (
    <section className="py-16">
      <div className="container mx-auto px-4">
        <h2 className="text-3xl font-bold text-center text-[#2D5A3D] mb-4">
          Productos Destacados
        </h2>
        <p className="text-center text-[#4A5568] mb-8">
          Los suplementos más populares de nuestra tienda
        </p>
        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
          {products.map((product) => (
            <ProductCard key={product.id} product={product} />
          ))}
        </div>
      </div>
    </section>
  );
}

export const layout = {
  areaId: 'content',
  sortOrder: 10
};

Account Pages (account/)

User account-related components:
src/pages/account/
├── Login.tsx
├── Register.tsx
└── Dashboard.tsx

Styling with Tailwind CSS

EverShop uses Tailwind CSS for styling. The Ana’s Suplements theme uses a custom color palette:
{/* Primary green */}
className="text-[#2D5A3D] bg-[#2D5A3D]"

{/* Hover state */}
className="hover:bg-[#1E3D2A]"

{/* Background */}
className="bg-[#F8FAF9]"

{/* Borders */}
className="border border-[#E8F5E9]"

{/* Text colors */}
className="text-[#4A5568]"
Use Tailwind’s utility classes for responsive design:
  • sm: - Small screens (640px+)
  • md: - Medium screens (768px+)
  • lg: - Large screens (1024px+)
  • xl: - Extra large screens (1280px+)

TypeScript Props

Always define TypeScript types for your component props:
type MyComponentProps = {
  title: string;
  description?: string;  // Optional prop
  items: Array<{
    id: string;
    name: string;
  }>;
  onSubmit?: (data: FormData) => void;
};

export default function MyComponent({ 
  title, 
  description, 
  items, 
  onSubmit 
}: MyComponentProps) {
  // Component implementation
}

Accessing EverShop Hooks

EverShop provides React hooks for common operations:
import { useCheckout } from '@evershop/evershop';

export default function PaymentMethod() {
  const checkout = useCheckout();
  const { paymentMethods, setPaymentMethods } = checkout;
  
  // Use checkout data and methods
}

Best Practices

Name components based on what they represent, not how they look:
  • Good: ProductCard, HeroSection, FeaturedProducts
  • Bad: GreenBox, BigText, ThreeColumns
Break large components into smaller, reusable pieces:
// Instead of one large component
<ProductGrid />

// Break it down
<ProductGrid>
  <ProductCard />
  <ProductCard />
</ProductGrid>
Avoid custom CSS files. Use Tailwind utility classes for all styling:
// Good
<div className="bg-white rounded-lg shadow-md p-6">

// Avoid
<div className="custom-card-style">
Use Tailwind’s spacing scale consistently:
  • p-4, p-6, p-8 for padding
  • mb-4, mb-6, mb-8 for margins
  • gap-4, gap-6, gap-8 for grid/flex gaps

anasuplements Theme Example

The included Ana’s Suplements theme demonstrates:
  • Custom color palette: Green (#2D5A3D) with pearl white backgrounds
  • Responsive design: Mobile-first layouts that adapt to larger screens
  • Component organization: Logical separation of homepage, account, and global components
  • TypeScript types: Proper type definitions for all props
  • Tailwind styling: Consistent use of utility classes
themes/anasuplements/
├── src/pages/
│   ├── all/
│   │   ├── Header.tsx       # Global navigation
│   │   └── Footer.tsx       # Global footer
│   ├── homepage/
│   │   ├── Hero.tsx         # Hero banner
│   │   ├── Features.tsx     # Feature highlights
│   │   └── FeaturedProducts.tsx  # Product showcase
│   └── account/
│       ├── Login.tsx        # Login form
│       ├── Register.tsx     # Registration form
│       └── Dashboard.tsx    # Account dashboard
├── package.json
└── tsconfig.json

Next Steps