Skip to content

Sections & layout ​

Sections & layout panel

The layout tree: a recursive array of "containers" (rows/columns) that can themselves contain other containers.

The sectionsDict dictionary: an object mapping each section name used in the layout to its settings (size, alignment, visibility...).

S stringN numberB boolean[] array{} object

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.

column"Board"row"Mana""Items"

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

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

The outer column stacks its two children top to bottom: first Board, then the row block. 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: it starts disabled and players can enable or disable it during the match. The key is the label shown for that toggle. Multiple optional nodes can be toggled together if they share the same key. { "key": "Sideboard zone" }
isSymmetricalForOpponentsBoptionalMirrors this node's content display order for the opponent's side of the board. Also mirrors style.marginLeft and style.marginRight. true
reverseForOppositeSideBoptionalUsed only inside sections.sharedZone.layout (2v2), in place of isSymmetricalForOpponents: reverses this node for players 3 & 4. true
json
{
  "direction": "row",
  "content": [
    { "section": "Bench", "style": { "width": "15vh" } },
    { "section": "Field" }
  ]
}

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. For example, a battlefield row in a two-team format. That's sections.sharedZone, a sibling of layout.

An optional zone shown once in the middle of the board, shared by all players instead of being duplicated per player like the rest of layout. Any section placed here lets every player put cards in it, though 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 isSymmetricalForOpponents. { "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 sectionsDict, not 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
isHiddenSrequiredDetermines who can see the cards when placed in this section. 'no' means everyone can see them, 'yes' means they are placed face down, and 'opponent-only' means they are face down only for opponents. A face-down card moved to another section remains face down.
possible values:noyesopponent-only
"no"
heightSrequiredSize of the section, one of the presets below (for prototyping), or a plain number to set an exact height in vh (recommended).
possible values:SMALLMEDIUMDEFAULT
"MEDIUM"
alignmentSrequiredHow cards align within the section.
possible values:STARTCENTERENDNONEDECK
"CENTER"
opponentAlignmentSoptionalOverrides alignment specifically for how card align 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"
isHorizontalAllowedBoptionalWill a horizontal card appear horizontally in this section, or will it be forced to appear like a normal card? true
displayedTitleSoptionalLabel shown above the section, if different from the section name/key. "Extra Deck"
noQuickActionsBoptionalDisables the small button to quickly select all cards in the section. true
noAutoPayToBoptionalCards played here will never trigger the automatic card cost payment. true
isGroupForbiddenBoptionalPrevents cards from being grouped below another card in this section. true
enterTappedBoptionalCards entering this section start tapped/rotated. true
enterTapped{}optionalCan also be defined as a map by card type. Cards entering this section will start tapped/rotated if their type is mapped to true. { "Unit": true, "Artifact": false }
keepTappedNewTurnBoptionalCards stay tapped across turn changes instead of auto-untapping. true
showHiddenCardInHistoryBoptionalA faced-down card place in this section will be revealed in the game history/log. true
logWhenPlayedBoptionalPosts a line in the chat log whenever a card enters this section. true
cardActionShortcut{}optionalShows a different one-click shortcut on the top left corner of 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 global cards.cardBackColor setting when this section is used as an extra deck for the card pile effect. "#c8bdb3"
json
"sectionsDict": {
  "Board": {
    "isHidden": "no",
    "height": "MEDIUM",
    "alignment": "CENTER"
  }
}

Common mistake

A section name used in layout (for example "section": "Field") 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, common in many card games) 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 clicks it from hand, or resolves it from the the stack.

FieldTypeRequiredDescriptionExample
autoPlayFromHand{}optionalWhere a card goes when played directly from hand, keyed by card type. Use "GROUP" as a destination to attach it to another card. { "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. Use "GROUP" as a destination to attach it to another card. { "Spell": "Discard" }
json
"autoPlayFromHand": {
  "Creature": "Board",
  "Resource": "Mana",
  "Spell": "Stack"
},
"autoPlayFromStack": {
  "Spell": "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, such as a life or 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.