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

# Blocks and nesting

> Repeatable items inside a section, block limits, and one-level nested blocks for menus and dropdowns.

Blocks are repeatable items inside a section: buttons, menu links, FAQ entries, feature cards. Sellers add, remove, hide and reorder them in Visual.

## Declaring blocks

```json schema.json theme={null}
{
  "name": "FAQ",
  "settings": [{ "type": "text", "id": "heading", "label": "Heading", "default": "Questions" }],
  "max_blocks": 20,
  "blocks": [
    {
      "type": "question",
      "name": "Question",
      "settings": [
        { "type": "text", "id": "question", "label": "Question", "default": "How fast is delivery?" },
        { "type": "richtext", "id": "answer", "label": "Answer", "default": "<p>Instantly, after payment.</p>" }
      ]
    }
  ],
  "presets": [{ "name": "FAQ", "blocks": [{ "type": "question" }, { "type": "question" }] }]
}
```

**type** `string` *required*

Block type, unique in the section.

**name** `string` *required*

Name shown in the editor.

**limit** `number`

Most blocks of this type in one section.

**settings** `Setting[]` *required*

Same setting types as sections. See [Section schema](https://docs.joinmarkt.com/themes/section-schema.md#setting-types).

`max_blocks` on the section caps all block types together.

## Blocks in templates

```json templates/index.json theme={null}
"faq": {
  "type": "faq",
  "blocks": {
    "q1": { "type": "question", "settings": { "question": "Do you refund?" } },
    "q2": { "type": "question", "disabled": true }
  },
  "block_order": ["q1", "q2"]
}
```

A block with `"disabled": true` stays in the file but does not render.

## Nested blocks

Set `"nested_blocks": true` on a section to let a block sit **one level** under another block. Menus with dropdowns are the common case.

In Visual, drag a block to the **right** to nest it under the block above, and to the **left** to take it out.

In the template, a child block names its parent's id:

```json groups/header.json theme={null}
"header": {
  "type": "header",
  "blocks": {
    "shop": { "type": "link", "settings": { "label": "Shop", "url": "/products" } },
    "keys": { "type": "link", "settings": { "label": "Game keys", "url": "/group/keys" }, "parent": "shop" },
    "gift": { "type": "link", "settings": { "label": "Gift cards", "url": "/group/gift" }, "parent": "shop" },
    "help": { "type": "link", "settings": { "label": "Support", "url": "/support" } }
  },
  "block_order": ["shop", "keys", "gift", "help"]
}
```

Nesting is one level deep. A child whose parent is hidden, missing or itself nested does not render.

### Reading nested blocks

Every block has `parent` (the parent's id, or null at the top level) and `children` (its enabled child blocks, in order).

> [tab] React
>
> ```tsx index.tsx theme={null}
> import type { SectionProps } from '@/platform'
>
> type Link = { label: string; url: string }
>
> export default function Header({ rootBlocks }: SectionProps<Record<string, never>, Link>) {
>   return (
>     <nav className="flex gap-6">
>       {rootBlocks.map((link) => (
>         <div key={link.id} {...link.attrs} className="group relative">
>           <a href={link.settings.url}>{link.settings.label}</a>
>           {link.children.length > 0 ? (
>             <div className="absolute hidden flex-col group-hover:flex">
>               {link.children.map((child) => (
>                 <a key={child.id} {...child.attrs} href={child.settings.url}>
>                   {child.settings.label}
>                 </a>
>               ))}
>             </div>
>           ) : null}
>         </div>
>       ))}
>     </nav>
>   )
> }
> ```

> [tab] Liquid
>
> ```liquid index.liquid theme={null}
> <nav class="flex gap-6">
>   {%- for link in root_blocks -%}
>     <div class="group relative" data-jm-block="{{ link.id }}">
>       <a href="{{ link.settings.url | url }}">{{ link.settings.label }}</a>
>       {%- if link.children.size > 0 -%}
>         <div class="absolute hidden flex-col group-hover:flex">
>           {%- for child in link.children -%}
>             <a data-jm-block="{{ child.id }}" href="{{ child.settings.url | url }}">{{ child.settings.label }}</a>
>           {%- endfor -%}
>         </div>
>       {%- endif -%}
>     </div>
>   {%- endfor -%}
> </nav>
> ```

> [tab] Vite
>
> ```ts index.ts theme={null}
> import type { RuntimeContext } from '@/platform'
>
> export function mount(el: HTMLElement, ctx: RuntimeContext) {
>   const nav = document.createElement('nav')
>   nav.className = 'flex gap-6'
>   for (const link of ctx.section.rootBlocks) {
>     const item = document.createElement('div')
>     item.setAttribute('data-jm-block', link.id)
>     const a = document.createElement('a')
>     a.href = String(link.settings.url ?? '/')
>     a.textContent = String(link.settings.label ?? '')
>     item.append(a)
>     for (const child of link.children) {
>       const sub = document.createElement('a')
>       sub.setAttribute('data-jm-block', child.id)
>       sub.href = String(child.settings.url ?? '/')
>       sub.textContent = String(child.settings.label ?? '')
>       item.append(sub)
>     }
>     nav.append(item)
>   }
>   el.replaceChildren(nav)
>   return () => el.replaceChildren()
> }
> ```

`blocks` still lists every enabled block, children included. Use `rootBlocks` (React, Vite) or `root_blocks` (Liquid) when you render a tree, so children are not drawn twice.


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