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

# Runtimes

> Write sections in React, Liquid or plain Vite modules. The same section in all three.

The `runtime` field in `theme.json` picks how section design files are written. All three build with the same Vite pipeline and the same platform, so settings, blocks, preview and publish behave the same. A theme may mix runtimes per section.

| `runtime` | Section file | Layout |
| - | - | - |
| `react` (default) | `src/sections/<type>/index.tsx`, default export | `src/theme.tsx` |
| `liquid` | `src/sections/<type>/index.liquid`, optional `script.ts` | Platform layout, or `src/theme.tsx` |
| `vite` | `src/sections/<type>/index.ts`, exports `mount(el, ctx)` | Platform layout, or `src/theme.tsx` |

Without `src/theme.tsx`, the files `src/theme.css` and `src/theme.ts` load automatically for global CSS and JS.

## One section, three runtimes

These files are exactly what the builder creates with **Code → New section** for a section named "Feature list" that starts **With repeatable items**. The schema is the same for every runtime:

```json src/sections/feature-list/schema.json theme={null}
{
  "name": "Feature list",
  "settings": [
    { "type": "text", "id": "heading", "label": "Heading", "default": "Feature list" },
    { "type": "textarea", "id": "text", "label": "Text", "default": "Tell your customers something here." }
  ],
  "max_blocks": 12,
  "blocks": [
    {
      "type": "item",
      "name": "Item",
      "settings": [
        { "type": "text", "id": "title", "label": "Title", "default": "Item" },
        { "type": "textarea", "id": "text", "label": "Text", "default": "A short line about it." }
      ]
    }
  ],
  "presets": [
    {
      "name": "Feature list",
      "blocks": [
        { "type": "item", "settings": { "title": "First" } },
        { "type": "item", "settings": { "title": "Second" } },
        { "type": "item", "settings": { "title": "Third" } }
      ]
    }
  ]
}
```

> [tab] React
>
> ```tsx src/sections/feature-list/index.tsx theme={null}
> import type { SectionProps } from '@/platform'
>
> type Settings = { heading: string; text: string }
> type Item = { title: string; text: string }
>
> export default function FeatureList({ settings, blocks }: SectionProps<Settings, Item>) {
>   return (
>     <div className="mx-auto max-w-6xl px-6 py-20">
>       <div className="mx-auto max-w-2xl text-center">
>         {settings.heading ? <h2 className="text-4xl font-bold tracking-tight">{settings.heading}</h2> : null}
>         {settings.text ? <p className="mt-4 text-lg opacity-75">{settings.text}</p> : null}
>       </div>
>       <div className="mt-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
>         {blocks.map((block) => (
>           <div key={block.id} {...block.attrs} className="rounded-2xl border border-current/10 p-6">
>             <h3 className="text-lg font-semibold">{block.settings.title}</h3>
>             <p className="mt-2 opacity-75">{block.settings.text}</p>
>           </div>
>         ))}
>       </div>
>     </div>
>   )
> }
> ```
>
> Spread `block.attrs` on each block's root element. It adds `data-jm-block`, which lets the editor select and highlight the block in the preview.

> [tab] Liquid
>
> ```liquid src/sections/feature-list/index.liquid theme={null}
> <div class="mx-auto max-w-6xl px-6 py-20">
>   <div class="mx-auto max-w-2xl text-center">
>     {%- if settings.heading != blank -%}<h2 class="text-4xl font-bold tracking-tight">{{ settings.heading }}</h2>{%- endif -%}
>     {%- if settings.text != blank -%}<p class="mt-4 text-lg opacity-75">{{ settings.text }}</p>{%- endif -%}
>   </div>
>   <div class="mt-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
>     {%- for block in blocks -%}
>       <div class="rounded-2xl border border-current/10 p-6" data-jm-block="{{ block.id }}">
>         <h3 class="text-lg font-semibold">{{ block.settings.title }}</h3>
>         <p class="mt-2 opacity-75">{{ block.settings.text }}</p>
>       </div>
>     {%- endfor -%}
>   </div>
> </div>
> ```
>
> Add `data-jm-block="{{ block.id }}"` to each block's root element so the editor can select it.

> [tab] Vite
>
> ```ts src/sections/feature-list/index.ts theme={null}
> import type { RuntimeContext } from '@/platform'
>
> /** Builds the section with DOM calls (textContent, never innerHTML with store text). */
> export function mount(el: HTMLElement, ctx: RuntimeContext) {
>   const values = ctx.section.settings
>   const wrap = document.createElement('div')
>   wrap.className = 'mx-auto max-w-6xl px-6 py-20'
>
>   const head = document.createElement('div')
>   head.className = 'mx-auto max-w-2xl text-center'
>   if (values.heading) {
>     const h2 = document.createElement('h2')
>     h2.className = 'text-4xl font-bold tracking-tight'
>     h2.textContent = String(values.heading)
>     head.append(h2)
>   }
>   if (values.text) {
>     const p = document.createElement('p')
>     p.className = 'mt-4 text-lg opacity-75'
>     p.textContent = String(values.text)
>     head.append(p)
>   }
>   wrap.append(head)
>
>   const grid = document.createElement('div')
>   grid.className = 'mt-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-3'
>   for (const block of ctx.section.blocks) {
>     const card = document.createElement('div')
>     card.className = 'rounded-2xl border border-current/10 p-6'
>     card.setAttribute('data-jm-block', block.id)
>     const h3 = document.createElement('h3')
>     h3.className = 'text-lg font-semibold'
>     h3.textContent = String(block.settings.title ?? '')
>     const p = document.createElement('p')
>     p.className = 'mt-2 opacity-75'
>     p.textContent = String(block.settings.text ?? '')
>     card.append(h3, p)
>     grid.append(card)
>   }
>   wrap.append(grid)
>
>   el.replaceChildren(wrap)
>   return () => el.replaceChildren()
> }
> ```
>
> `mount` returns a cleanup function. Without an `update` export, the section remounts when its data changes.

## React

A section is the default export of `index.tsx`. It receives `SectionProps`:

**id** `string`

Section instance id.

**type** `string`

Section type (folder name).

**template** `string`

Template being rendered: `index`, `product`, `page.about`…

**settings** `object`

Resolved settings: stored values over schema defaults.

**blocks** `BlockProps[]`

Every enabled block in order, nested ones included.

**rootBlocks** `BlockProps[]`

Top-level blocks only. Nested blocks are in each block's `children`.

Import store data and helpers from `@/platform`, for example `useStore`, `useRouteProduct`, `useCart`, `useCurrency`, `useGlobalSettings`, `Media`, `RichText` and `ThemeIcon`.

## Liquid

Liquid sections use [liquidjs](https://liquidjs.com) with Shopify syntax.

* `settings` is this section's values. `theme` is the global theme settings.
* `blocks` (also `block_list`) lists every enabled block. `root_blocks` lists top-level blocks only.
* `{{ }}` output is HTML-escaped. Use `| raw` only for `richtext` and `html` settings: `{{ section.settings.body | raw }}`.
* Filters: `money`, `money_without_currency`, `url`, `image_url`, `asset_url`, `handleize`, `json`, `stars`, plus every liquidjs built-in.
* Use `| url` for every internal link. It produces a store-relative link that works in the preview and on the live store.
* Snippets live in `snippets/<name>.liquid`: `{% render 'product-card', product: p %}`.
* Markup re-renders when data changes (cart, preview edits). Put animation code in `script.ts`. It exports `mount(el, ctx)`, returns a cleanup, and runs again after each render.

## Vite

A Vite section exports `mount(el, ctx)` and, optionally, `update(el, ctx)` to patch the DOM instead of remounting.

```ts theme={null}
import type { RuntimeContext } from '@/platform'

export function mount(el: HTMLElement, ctx: RuntimeContext) {
  // build DOM
  return () => {}
}

export function update(el: HTMLElement, ctx: RuntimeContext) {
  // optional: patch DOM on data change
}
```

Build DOM with `createElement` and `textContent`. Never put store text into `innerHTML`. Internal links use store-relative paths such as `/product/slug`; the platform routes them.

## Runtime data

Liquid sees these as top-level variables. Vite modules get them as `ctx`.

| Key | Contents |
| - | - |
| `settings` | Liquid: this section's values. Vite: global theme settings. |
| `theme` | Liquid: global theme settings. |
| `section` | `id`, `type`, `settings`, `blocks`, `rootBlocks`. In Vite, section values are `ctx.section.settings`. |
| `template` | `index`, `product`, `page.about`… |
| `shop` | Store info: name, logo, description, currency… |
| `products` | Every product with `title`, `url`, `image`, `price`, `compare_at_price`, `on_hold`, `sold_out`, `variants`… |
| `groups` | Product groups with `title`, `url`, `products`. |
| `catalog` | Home and grid items, groups first. |
| `product` / `group` | The current route's product or group. |
| `categories`, `reviews`, `stats` | Store categories, feedback and stats. |
| `cart` | `items`, `count`, `subtotal`, `discount`, `total`, `open`, `coupon`, `coupon_error`, `coupons_enabled`. |
| `currency`, `loading` | Display currency, and whether the catalog is still loading. |

To add to cart, open the cart or check out from Liquid and Vite markup, see [Commerce attributes](https://docs.joinmarkt.com/themes/commerce-attributes.md).


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