Skip to content

Sections & layout

Sections & layout panel

This is the densest part of the file: three different structures work together under the sections key. This chapter takes them one at a time.

S stringN numberB boolean[] array{} object

Why arrays?

The confusion almost always comes from the same place: seeing [ ] where { } was expected. The rule:

Rule of thumb

Whenever the game can have more than one of something (several sections shown side by side, several tokens, several formats...), it's an array [ ] of objects, one per instance. Whenever there's only one of something at that level (the mulligan settings, the general info), it's an object { }.

Three arrays of objects intersect under sections, and it's their nesting — not any one of them alone — that makes the whole thing hard to read at a glance:

  1. The layout tree — a recursive array of "containers" (rows/columns) that can themselves contain other containers.
  2. The sectionsDict dictionary — not an array, an object mapping each section name used in the layout to its settings (size, alignment, visibility...).
  3. Custom sections — a special section type whose visual content is itself described by a tree of objects (a small UI language).

The layout tree

sections.layout describes the visual arrangement of the board: which sections sit side by side, which are stacked. direction controls this literally — "column" stacks its children top to bottom, "row" places them side by side, left to right. A node can nest, so a column can contain a row as one of its children:

"direction": "column""direction": "row""Mana""Items""Board"

That diagram is the visual result of this exact JSON — a column with two children: a nested row, then a section:

json
{
  "direction": "column",
  "content": [
    {
      "direction": "row",
      "content": [
        { "section": "Mana" },
        { "section": "Items" }
      ]
    },
    { "section": "Board" }
  ]
}

The outer column stacks its two children top to bottom — first the row block, then Board. Inside that row block, direction: "row" places Mana and Items side by side. That's the whole mechanism: nest rows and columns to build any arrangement, exactly like nested <div>s on a web page.

Every node in the tree has the same shape:

One node of the recursive layout tree — either a container (row/column) or a leaf pointing at a real section.

FieldTypeRequiredDescriptionExample
directionSoptionalHow this container's children are placed. Absent on a leaf.
possible values:rowcolumn
"row"
content[]optionalChildren of this container. Each item is either another container or a leaf { "section": "..." }. [{ "section": "Hand" }]
sectionSoptionalLeaf only: the real section name, must exist as a key in sectionsDict. "Hand"
style{}optionalRaw CSS styles applied to this node. { "width": "20%" }
optional{}optionalMakes this node an opt-in toggle: players can turn it on or off before the match starts. key is the label shown for that toggle. { "key": "Sideboard zone" }
isSymetricalForOpponentsBoptionalMirrors this node's position for the opponent's side of the board. true
reverseForOppositeSideBoptionalUsed only inside sections.sharedZone.layout (2v2), in place of isSymetricalForOpponents: reverses this node vertically for players 3 & 4. true
json
{
  "direction": "row",
  "content": [
    { "section": "Commander", "style": { "width": "15vh" } },
    { "section": "Creatures" }
  ]
}

Building your own

Start with a single leaf ({ "content": [{ "section": "Board" }] }), confirm it renders, then add one section at a time. Building the tree incrementally beats trying to write it all in one pass.

Shared zone

Some games have a zone that only exists once, shared by every player, instead of being duplicated on each side of the board — e.g. a battlefield row in a two-headed-giant format. That's sections.sharedZone, a sibling of layout:

An optional zone shown once in the middle of the board, shared by all players (e.g. a battlefield row) instead of being duplicated per player like the rest of layout. Any section placed here lets every player put cards in it — players can still only interact with their own cards. Sections used here still need an entry in the same sectionsDict as regular sections.

FieldTypeRequiredDescriptionExample
heightNrequiredHeight of the shared zone, in vh. 14
layout{}requiredA layout tree, same node shape as sections.layout. For 2v2 boards, use reverseForOppositeSide on a node instead of isSymetricalForOpponents. { "direction": "column", "content": [ ... ] }
json
"sharedZone": {
  "height": 14,
  "layout": {
    "direction": "column",
    "content": [{ "section": "Attackers" }]
  }
}

sharedZone.layout uses the exact same node shape as layout above — it's a separate tree, not a special node type.

sectionsDict

Once a section is placed in the layout tree, you still need to define how it behaves — is it hidden from the opponent, how big, how aligned? That's sectionsDictnot an array, an object keyed by the exact name used in the layout.

The behaviour of one named section referenced by the layout tree.

FieldTypeRequiredDescriptionExample
isHiddenSrequiredWho can see the cards in this section.
possible values:noyesopponent-only
"no"
heightSrequiredSize of the section — one of the presets below, or a plain number to set an exact height in vh.
possible values:SMALLMEDIUMDEFAULT
"MEDIUM"
alignmentSrequiredHow cards align within the section.
possible values:STARTCENTERENDNONEDECK
"CENTER"
opponentAlignmentSoptionalOverrides alignment specifically for how this section appears on the opponent's side. "END"
heightReferenceSoptionalSet to "horizontal" so this section's height is measured against a horizontal (rotated) card instead of an upright one — useful for a row meant only for tapped/rotated cards.
possible values:horizontal
"horizontal"
isHorizontalAllowedBoptionalCan a card be rotated horizontally in this section (e.g. to show it's tapped)? true
displayedTitleSoptionalLabel shown above the section, if different from the technical name. "Extra Deck"
noQuickActionsBoptionalDisables the right-click quick action menu on this section. true
noAutoPayToBoptionalExcludes this section from being auto-selected when the engine auto-pays a card's cost. true
enterTappedBoptionalCards entering this section start tapped/rotated. true
isGroupForbiddenBoptionalDisallows grouping multiple cards together into a single stack in this section. true
keepTappedNewTurnBoptionalCards stay tapped across turn changes instead of auto-untapping. true
showHiddenCardInHistoryBoptionalOnce a hidden card is revealed, shows its identity in the game history/log. true
logWhenPlayedBoptionalPosts a line in the chat log whenever a card enters this section. true
isDefaultSectionBoptionalUsed only when overriding one of the engine's built-in sections (its default display, e.g. the stack) rather than defining a new custom one. true
cardActionShortcut{}optionalA one-click shortcut icon shown on cards in this section, e.g. to move them straight to another section. { "action": "MOVE", "actionData": { "destination": "Discard" } }
drawDestinations{}optionalFor a deck-like section (e.g. an extra deck): where a card goes when drawn from it, either by click or at new turn, keyed by card type. Use "_default" for any type not explicitly listed. { "_default": "Stack", "Creature": "Board" }
cardBackColorSoptionalOverrides the game-wide cards.cardBackColor for this section specifically — useful for an extra deck whose cards use a different-colored back. "#c8bdb3"
json
"sectionsDict": {
  "Board": {
    "isHidden": "no",
    "height": "MEDIUM",
    "alignment": "CENTER"
  }
}

Common mistake

A section name used in layout (e.g. "section": "Creatures") with no matching entry in sectionsDict won't render correctly. The two structures must stay in sync: layout says where, the dictionary says how.

No spaces in section names

Every key in sectionsDict (and every matching "section": "..." in layout) must be a single word with no spaces — use Extra_Deck or ExtraDeck, never Extra Deck. Use displayedTitle if you want a label with spaces shown to the player.

Extra decks

An "extra deck" (a second, separate deck players draw or play from — e.g. MTG's sideboard-like zones, Yu-Gi-Oh's Extra Deck) is just a sectionsDict entry with alignment: "DECK". Most of the optional fields aren't needed for a basic one:

json
"sectionsDict": {
  "Extra-deck": {
    "isHidden": "yes",
    "height": "MEDIUM",
    "alignment": "DECK",
    "displayedTitle": "Extra deck"
  }
}

Clicking a card in this section plays it using the section's autoPlayFromHand value (below). Use drawDestinations on the same entry if drawn cards should scatter to different sections depending on card type instead of a single default.

Auto-play destinations

autoPlayFromHand and autoPlayFromStack are dictionaries, not arrays — they map a card type to the section it lands in, so each type only needs one destination:

Dictionaries mapping a card type to the section it should land in when the player plays it — from hand, or from the resolving stack. Not arrays: one entry per card type, since each type has exactly one default destination.

FieldTypeRequiredDescriptionExample
autoPlayFromHand{}optionalWhere a card goes when played directly from hand, keyed by card type. Use "GROUP" as a destination to auto-attach it to another card instead of a section. { "Creature": "Board", "Item": "GROUP" }
autoPlayFromStack{}optionalWhere a card goes once it finishes resolving from the stack, keyed by card type — typically "Discard" for spells, or a permanent's board section. { "Spell": "Discard" }
json
"autoPlayFromHand": {
  "Creature": "Board",
  "Land": "Mana",
  "Sorcery": "Stack"
},
"autoPlayFromStack": {
  "Sorcery": "Discard"
}

Custom sections

A custom section isn't a separate structure — it's a normal sectionsDict entry, just with "type": "custom" instead of the usual isHidden/height/alignment fields. Use it for a section that renders a small UI instead of cards — e.g. a life/mana counter panel — described as a tree of elements (much like HTML) via blueprint.

Not a separate structure — this is a normal sectionsDict entry, just with type: "custom" instead of the usual isHidden/height/alignment fields, which renders a small UI (e.g. a counter panel) instead of cards.

FieldTypeRequiredDescriptionExample
typeSrequiredMust be "custom" to enable this mode.
possible values:custom
"custom"
defaultValue{}optionalInitial state stored for this section, later accessible via game.data.<SectionName>. { "white": 0, "blue": 0 }
blueprint{}requiredThe UI element tree (type, props, children) that renders this section. onChange expressions have access to value (the new value), delta (the change amount), and a chatLog(message) helper to post to the game log. { "type": "div", "children": [...] }
isSharedBoptionalIf true, this custom section's state is shared/visible across all players rather than being per-player. true
events{}optionalScript expressions run on specific triggers.
possible values:onStartonNewTurnonOpponentUpdate
{ "onNewTurn": "game.data.Boost.value = 0" }
json
"sectionsDict": {
  "Counters": {
    "type": "custom",
    "defaultValue": { "white": 0, "blue": 0 },
    "blueprint": {
      "type": "div",
      "children": [
        {
          "type": "input-number",
          "props": { "value": "{{ game.data.Counters.white }}" },
          "onChange": "game.data.Counters.white = value"
        }
      ]
    }
  }
}

A custom section can also react to game events instead of only user input — see the events row in the table above (onStart, onNewTurn, onOpponentUpdate).

Placement matters

Custom sections are often positioned with absolute CSS (position: absolute) so they can float over the board. Place them last in your layout tree — otherwise a section defined after them can end up rendered on top and block their inputs from being clicked.

This mode is more technical than the rest of the file. For a first game, it's fine to define only plain sections and add a custom one later if you need a specific widget.

Naming conventions

A handful of section names carry built-in meaning, or are reserved outright — no extra config needed beyond naming the section this way (or avoiding the name) in sectionsDict:

Special name: "Mana"

Naming a section Mana makes the engine display a simple card-count indicator for that zone instead of rendering full cards — useful for a resource pool where only the count matters, not which cards are in it.

Reserved names

Stack, Hand, Deck, Discard, Sideboard, Remove, and RemoveHidden are used internally by the engine. Don't use them as the name of a new custom section — only reference them in sectionsDict if you're deliberately overriding one of the engine's own built-in sections.

Cheat sheet

StructureRole
layoutVisual arrangement — who sits next to who. A tree of rows/columns, duplicated per player.
sharedZoneSame tree shape as layout, but rendered once and shared by all players.
sectionsDictBehaviour of each named section — size, visibility, alignment.
blueprintCustom sections only — a small UI described as a tree of elements.

Not sure if something should be an array?

Ask: "can this spot in the game legitimately hold more than one thing side by side?" If yes → array [ ]. If it's a fixed set of settings for one single thing → object { }.