Astral Realms Documentation Help

Menu Layouts

Layouts dynamically fill a set of slots with items from a collection — the primary tool for building paginated lists, inventories, leaderboards, and any other variable-length content.

How Layouts Work

  1. A LayoutBlueprint is defined in the menu's layouts: map.

  2. At compute time a LayoutInstance is created and bound to a data provider.

  3. Items tagged with the layout's taint are distributed across the layout's slots.

  4. Pagination is handled via next-page, previous-page, and first-page actions.

Blueprint Fields

layouts: items: # layout identifier provider: "%parameters_items%" # placeholder that resolves to the data source taint: "shop-item" # items with this taint tag are rendered here view-requirements: [] # optional — hide the layout entirely when these fail sort: # optional — order the entries before they are paginated by: "%parameter_offer_price%" order: DESCENDING

Field

Type

Description

provider

String (placeholder)

Resolves to a Collection<Placeholder>, Map<?, ?>, or ItemProvider.

taint

String

Only MenuItem entries with this taint string are placed inside this layout.

view-requirements

List<Requirement>

When any fails the layout renders nothing.

refresh

object

Optional timer that re-runs the provider and redistributes items (see Auto-refresh).

sort

object

Optional. Orders the provider entries before pagination (see Sorting).

Data Providers

The provider placeholder must resolve to one of:

Type

Description

Collection<Placeholder>

Each element is rendered as one layout slot. The element's placeholder context is active while rendering that slot's item.

Map<?, ?>

Each key-value pair is exposed as a placeholder context per slot.

ItemProvider

Custom Java interface for programmatic item generation.

Providers are typically passed as parameters when opening the menu:

menus.computeAndOpen(player, "shop", Map.of("items", myItemCollection));

Slot Assignment

Layout slots are defined by associating items with the layout's taint. The slots those items occupy become the layout's available slots. Items are distributed in slot-index order.

items: shop-row-1: slots: [10, 11, 12, 13, 14, 15, 16] taints: - "shop-item" item-stack: material: "STONE" # placeholder/template item — overridden by layout data

Slots claimed by an input slot are excluded: a slot the player owns can never be rendered into, so it never becomes one of the layout's available slots.

Overlapping layouts

Two layouts may cover the same slot. Ownership is not decided when the layouts are built — it is claimed by whichever one last rendered into the slot, and only that one may clear or refresh it. A layout whose view-requirements stop passing tears itself down without blanking a slot another layout has since drawn into.

This makes "one of these two lists, depending on state" a supported pattern: give both layouts the same slots and keep them apart with mutually exclusive view-requirements. Overlap where the requirements do not keep them apart is not rejected either: nothing is logged and the last render simply wins, so a shared slot that flickers between two layouts points at requirements that both pass.

Entry Placeholders

While a provider entry is being rendered — and while its sort key is being evaluated — the entry is exposed under the parameter namespace, on top of the menu's own placeholders.

Placeholder

Description

%parameter_index%

Index of the entry in the backing provider. Not renumbered by sorting: an entry keeps its original provider index after being reordered.

%parameter_slot%

Inventory slot the entry is being rendered into. -1 while view requirements and sort keys are evaluated, since no slot is assigned yet.

%parameter_<namespace>_<field>%

The entry itself, under its own placeholder namespace (e.g. %parameter_offer_price% for an entry whose namespace is offer).

Sorting

A layout can order its provider entries before they are paginated via a sort: block. Useful for lists whose provider returns an arbitrary order (market listings by price, leaderboards by score).

layouts: items: provider: "%parameters_items%" taint: "shop-item" sort: by: "%parameter_offer_price%" # sort key — required to activate order: DESCENDING # ASCENDING or DESCENDING

Field

Type

Description

by

String (placeholder)

Sort key, resolved inside the comparator — once per entry per comparison, not cached. Required to activate — nothing is sorted when absent. Must resolve to a Comparable.

order

ASCENDING \|DESCENDING

Sort direction, uppercase. Required whenever by is set. Accepts a placeholder.

Sorting runs after the view-requirement pass that decides which entries are visible, and before the page count and the page slice are computed — so the ordering is global across all pages, not a per-page shuffle.

by is evaluated in the entry's own context, which is what makes %parameter_<namespace>_…% (and %parameter_index%) usable as a sort key; %parameter_slot% is -1 during that evaluation, so it is meaningless as a key. order is resolved once per compute in the menu context instead, so menu-level placeholders work there but per-entry %parameter_…% do not.

Because the key is re-resolved on every comparison rather than computed once per entry, keep by cheap — a plain provider field, not an $e(...) expression over several placeholders.

Pagination

When the provider has more items than available slots, the layout is automatically paginated. Pages are cut out of the sorted entry list, so navigating pages walks the sorted order.

items: next-page-btn: slot: 53 item-stack: material: "ARROW" name: "<green>Next page" actions: LEFT: - "[next-page] items" prev-page-btn: slot: 45 item-stack: material: "ARROW" name: "<red>Previous page" actions: LEFT: - "[previous-page] items"

The current page number is accessible via %layouts_items_page% and the total page count via %layouts_items_maxPages%.

A full menu refresh via the refresh action keeps the viewer on the page they were on: the page is carried across the recompute and clamped to the new page count, so a shrinking provider lands on the last available page rather than resetting to the first.

Auto-refresh

A layout can re-run its provider and redistribute items on a repeating timer via a refresh: block — the layout-level analogue of item auto-refresh. Useful for live lists (online players, leaderboards, market listings) that change without a click.

layouts: items: provider: "%parameters_items%" taint: "shop-item" refresh: delay: 2.0 # seconds — required to activate requirements: [] # optional actions: [] # optional

Field

Type

Description

delay

double (seconds)

Interval between refreshes, in seconds. Accepts a placeholder / $e(...). Inactive if absent or <= 0.

requirements

List<Requirement>

Optional. Evaluated every tick; if any fails, that tick is skipped (the timer keeps running).

actions

List<Action>

Optional. Run every tick that requirements pass, immediately before the layout is re-rendered.

Each tick re-resolves the provider and redistributes items across the layout's slots — the timer-driven equivalent of a refresh-layout action.

Layout Placeholders

%layouts_<id>% resolves to the live layout instance, and any remaining tokens drill into it.

Placeholder

Description

%layouts_<id>_id%

Layout identifier

%layouts_<id>_page%

Current page index (0-based)

%layouts_<id>_maxPages%

Total number of pages

%layouts_<id>_hasNextPage%

true when a next page exists

%layouts_<id>_hasPreviousPage%

true when a previous page exists

These keys are commonly combined with view-requirements to hide navigation buttons when no further pages exist, e.g. [compare] %layouts_items_hasNextPage% == true.

Last modified: 03 September 2026