> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joinmarkt.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a React theme

> Build a Join Markt theme with React sections, from an empty folder to a live store.

A React theme is a folder. Each section is one React component plus one `schema.json`. The platform gives every section its settings and blocks as props.

> [!NOTE]
>
> Real example: **Pulse** in `templates/themes/pulse/`. Copy it if you want a head start.

## The folder

```text theme={null}
my-theme/
├── theme.json              # name, id, runtime
├── index.html              # loads the platform entry
├── tailwind.config.ts      # optional
├── config/
│   ├── settings_schema.json  # theme settings (colors, fonts…)
│   └── settings_data.json    # their saved values
├── groups/
│   ├── header.json         # sections shown on every page (top)
│   └── footer.json         # sections shown on every page (bottom)
├── templates/
│   ├── index.json          # home page  (required)
│   ├── product.json
│   ├── products.json
│   └── …                   # cart, group, reviews, support, policy, 404
├── public/                 # images, icons
└── src/
    └── sections/
        └── hero/
            ├── index.tsx   # the design
            └── schema.json # the settings
```

> [!NOTE]
>
> `src/platform/` is not yours. The platform writes it at build time. Any copy you upload is ignored.

## Steps

**1. Write theme.json**

```json theme.json theme={null}
{
  "engine": "join-markt/v2",
  "id": "my-theme",
  "name": "My Theme",
  "version": "1.0.0",
  "runtime": "react"
}
```

`engine`, `id`, `name` and `version` are required. `runtime` defaults to `react`.

**2. Write index.html**

It must load `/src/platform/entry.tsx`. Without it, the upload is blocked.

```html index.html theme={null}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Store</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/platform/entry.tsx"></script>
  </body>
</html>
```

**3. Add a section**

The settings the seller sees:

```json src/sections/spacer/schema.json theme={null}
{
  "name": "Spacer",
  "category": "layout",
  "settings": [
    { "type": "range", "id": "height", "label": "Height", "min": 8, "max": 240, "step": 8, "unit": "px", "default": 48 },
    { "type": "range", "id": "mobile_height", "label": "Height on mobile", "min": 8, "max": 240, "step": 8, "unit": "px", "default": 32 }
  ],
  "presets": [{ "name": "Spacer" }]
}
```

The design. It gets `settings` as props:

```tsx src/sections/spacer/index.tsx theme={null}
import type { CSSProperties } from 'react'
import type { SectionProps } from '@/platform'

export default function Spacer({ settings }: SectionProps<{ height: number; mobile_height: number }>) {
  const style = {
    '--spacer': `${settings.mobile_height}px`,
    '--spacer-md': `${settings.height}px`,
  } as CSSProperties
  return <div aria-hidden="true" style={style} className="h-[var(--spacer)] md:h-[var(--spacer-md)]" />
}
```

This is the real Spacer from Nova.

**4. Put it on the home page**

```json templates/index.json theme={null}
{
  "sections": {
    "spacer": { "type": "spacer", "settings": { "height": 64 } }
  },
  "order": ["spacer"]
}
```

`type` is the folder name under `src/sections/`.

**5. Zip and upload**

Zip the folder and upload it in the theme hub. One top folder inside the ZIP is fine. See [Upload rules](https://docs.joinmarkt.com/themes/upload-rules.md).

**6. Edit and publish**

The theme opens in **Visual** and **Code**. Press **Publish** to put it live.

## What a section gets

| Prop | What it is |
| - | - |
| `settings` | the values from `schema.json`, as the seller set them |
| `blocks` | repeatable items, in order. See [Blocks](https://docs.joinmarkt.com/themes/blocks.md) |

Product, cart and store data come from `@/platform`. See the list below.

## Platform API

Import everything from `@/platform`. You never write API calls yourself.

| Need | Import |
| - | - |
| Store, products, groups | `useStore()` |
| The product or group of this page | `useRouteProduct()`, `useRouteGroup()` |
| Cart: add, remove, total, coupon | `useCart()` |
| Prices in the buyer's currency | `useCurrency()` |
| Go to checkout | `goToPlatformCheckout(items)` |
| Theme settings (colors, fonts) | `useGlobalSettings()` |
| Images and videos | `<Media />` |
| Seller's rich text | `<RichText />` |
| Icons | `<ThemeIcon />` |
| Fade-in on scroll | `<Reveal />` |
| Page title and SEO | `usePageTitle()`, `usePageSeo()` |
| Buyer login | `useStoreCustomerAuth()` |
| Helpers | `cn`, `getDefaultVariant`, `getProductPriceRange`, `buildCatalogItems`, `formatDate` |

```tsx theme={null}
import { useStore, useCurrency, getProductPriceRange } from '@/platform'

export default function FirstProduct() {
  const { products } = useStore()
  const { formatPrice } = useCurrency()
  const product = products[0]
  if (!product) return null
  return <p>{product.name} · {formatPrice(getProductPriceRange(product).min)}</p>
}
```

Plain `data-jm-*` attributes work in React too. See [Commerce attributes](https://docs.joinmarkt.com/themes/commerce-attributes.md).

## Packages

You can import only the packages our own themes use, like `react`. Any other package blocks the upload. Install scripts (`postinstall` and others) are blocked too.

## Next

> [card] [Upload rules](https://docs.joinmarkt.com/themes/upload-rules.md)
>
> What gets kept, and every error.

> [card] [Section schema](https://docs.joinmarkt.com/themes/section-schema.md)
>
> Every setting type.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.