Skip to content

Grid

Arranges its children into cells of a fixed size, left to right and then top to bottom. Parchment's other containers stack vertically or place by coordinate, so this is the one that lays anything out across the page.

{
  "Type": "Grid",
  "Columns": 6,
  "CellWidth": 20,
  "CellHeight": 20,
  "ColumnSpacing": 2,
  "RowSpacing": 2,
  "Children": [
    { "Type": "Image", "ItemId": "(O)145", "Scale": 3, "Alignment": "Center", "VerticalAlignment": "Center" },
    { "Type": "Image", "ItemId": "(O)147", "Scale": 3, "Alignment": "Center", "VerticalAlignment": "Center" }
  ]
}

Grid fields

Property Type Default Description
Columns required int 1 How many cells sit side by side before the next row starts.
CellWidth required int A cell's width in unscaled sprite pixels × Scale.
CellHeight required int A cell's height in unscaled sprite pixels × Scale.
Rows optional int? as many as needed The most rows drawn. Children past the last cell are dropped. See Capping the rows.
ColumnSpacing optional int 0 Space between columns, in unscaled sprite pixels × Scale. Not applied outside the outermost columns.
RowSpacing optional int 0 Space between rows, in unscaled sprite pixels × Scale.
Padding optional int 0 Space between the cells and the grid's border.
Children optional list of elements empty list The elements filling the cells, in order. Ignored when Source is given.
Source optional source Fills the cells from an item query instead of from Children, narrowed by what the reader types. See Source.
Background optional list of elements empty list Elements drawn behind the cells, placed by Position within the grid's content area. They don't affect its size.
Foreground optional list of elements empty list Elements drawn over the cells, placed by Position.

A grid's size is declared, not measured. Its width is Columns × CellWidth plus the spacing between them, and its height follows from how many rows the children fill. That's why there's no Sizing or Width here: a cell is the same size as every other cell, so one child can never resize the rest.


Cells are boxes, not moulds

A child isn't stretched to fill its cell. It's measured normally and then anchored within the cell by its own Alignment and VerticalAlignment, with Position nudging it from there.

The default is Left and Top, so children hug the top-left of their cells unless you say otherwise. For a grid of icons you almost always want both centred:

{ "Type": "Image", "ItemId": "(O)145", "Scale": 3, "Alignment": "Center", "VerticalAlignment": "Center" }

A child larger than its cell isn't clipped, it overhangs into the neighbouring one. Size the cells to the largest thing going in them.

Hidden cells close up

A child whose Condition fails takes no cell at all. The children after it move up to fill the gap, so the grid packs rather than leaving a hole, the same way a hidden element lets a stack close up.

That's what makes a grid filter cleanly: condition each cell and the remaining ones gather at the start rather than scattering.

When you want the hole

A fixed layout where one entry isn't unlocked yet wants the gap kept. Use a placeholder cell instead of hiding: an Image pointing at a blank or greyed sprite, swapped by the condition rather than removed by it.

Capping the rows

Rows fixes the grid's height and drops anything past the last cell, the way a page drops content that runs past its bottom. A dropped child logs a trace message rather than failing.

{
  "Type": "Grid",
  "Columns": 6,
  "Rows": 5,
  "CellWidth": 20,
  "CellHeight": 20
}

Without Rows, the grid is exactly as tall as its children need and grows a row at a time. A grid inside a page's Elements is stacked like anything else, so an uncapped grid that outgrows the page triggers the usual overflow warning.

Source

Source fills the cells from an item query rather than from authored children, and narrows them by an Input's text. It's how a search grid works, and the reason it works without reflowing anything is that the number of cells never changes. Only what each cell shows does.

{
  "Type": "Grid",
  "Id": "fish",
  "Columns": 6,
  "Rows": 5,
  "CellWidth": 20,
  "CellHeight": 20,
  "Source": {
    "ItemQuery": "ALL_ITEMS (O)",
    "PerItemCondition": "ITEM_CATEGORY Target -4",
    "InputId": "search",
    "OrderBy": "Name",
    "Template": {
      "Type": "Image",
      "Scale": 3,
      "Alignment": "Center",
      "VerticalAlignment": "Center",
      "Action": "PeacefulEnd.Parchment_JumpToPageId %Item%"
    }
  }
}
Property Type Default Description
Template required element What each cell is built from. One template makes every cell.
ItemQuery optional string ALL_ITEMS (O) The item query supplying the candidates. Resolved once and cached, so this is paid on load rather than per keystroke.
PerItemCondition optional string A game state query each candidate must pass, evaluated with that item in context. Category filters belong here.
InputId optional string The input whose text narrows the candidates. Without one the grid is an unfiltered list.
OrderBy optional item property | None None The property the candidates are sorted by before they reach the cells. None leaves them in the item query's own order. See Ordering.
OrderDescending optional bool false Reverses the order, so the highest price or the last name comes first.
Count optional int? Columns × Rows How many cells the candidates fill. Needed only when the grid has no Rows.

Ordering

OrderBy takes any of the item properties the %Item.Something% token reaches, so "Name", "Category" and "Price" are all valid. It defaults to "None", which leaves the candidates in whatever order the item query handed back, so a grid sorts only when it asks to.

content.json
"Source": {
  "ItemQuery": "ALL_ITEMS (O)",
  "OrderBy": "Price",
  "OrderDescending": true,
  "Template": { "Type": "Image", "Scale": 3, "Alignment": "Center" }
}

Each property declares how it compares, so Price sorts as a number (9 before 1000) while Name and the rest sort as text, ignoring case. The item properties table says which is which.

Sorting happens once, when the item query is resolved, rather than on each keystroke. A grid ordering 1,000 items pays for it on load and the filter then walks an already-sorted list.

Items that can't answer go last

An item with no category, or a price that isn't a number, sorts to the end. OrderDescending doesn't move them, so reversing the order never brings a wall of blank-looking cells to the front.

How a cell gets its item

The item is applied to any Image inside the template that has neither an ItemId nor a TexturePath of its own. That's the hole the result fills. An Image with its own texture is authored art, such as a slot frame behind the icon, and is left alone.

So a cell can be more than an icon. Make the template a Panel and everything inside it comes along:

"Template": {
  "Type": "Panel",
  "Children": [ { "Type": "Image", "Scale": 3, "Alignment": "Center" } ],
  "Background": [ { "Type": "Image", "TexturePath": "{{ModId}}/slot", "Scale": 4 } ]
}

DisplayName and Description are filled from the item wherever the template leaves them out, so tooltips work with no extra authoring. Set them to "" in the template to suppress that.

%Item% in an action resolves to the cell's qualified item ID, which is how one template's action reaches whichever result its cell landed on.

What filtering does

Typing narrows the candidates by display name and by qualified item ID, ignoring case. Matches fill the cells from the first, and cells past the last match are emptied and hidden. An empty box matches everything.

You see the first Count matches, not all of them

A filter matching 112 items across 30 cells shows 30. That's the trade a fixed cell count buys: nothing reflows and the page count never changes.

Say so with tokens. They read the grid's own Id, the one alongside Type rather than anything inside Source, so a grid you want to report on needs one:

{
  "Type": "Paragraph",
  "Text": "Showing %GridDisplayed:fish% of %GridMatched:fish% matches."
}

Inside the template, %Item.Name% and its siblings let a cell label itself with the item it landed on.

Children is ignored

A grid with Source builds its cells from Template alone. Anything in Children is not drawn, rather than being appended after the results.

Common fields

Scale on a Grid multiplies the cell size, the spacing and the padding together, so raising it enlarges the whole grid rather than only its frame.

Property Type Default Description
Type required element type Which kind of element this is. Determines every other field below.
Id optional string An optional identifier for this element. Not used for navigation, purely for your own reference.
Alignment optional Left | Center | Right Left Where the element sits within its container's width. Only has an effect when the element is narrower than the space available (see Layout). In a placed list it anchors the element and Position is measured from there.
VerticalAlignment optional Top | Center | Bottom Top Where the element sits within its container's height. Only used in Background, Foreground, Underlay and Overlay, since a stacked element takes its vertical position from the elements above it (see Placed elements). Setting it on a stacked element logs an error and does nothing.
Scale optional number 1 The element's scale. Its meaning depends on the element type: sprite scale for Image, Panel, Banner, Button and Divider. Font scale for Title, Heading and Paragraph. See Units and scale.
SpacingAfter optional int 8 The gap between this element and the next one, in unscaled sprite pixels × Scale. Not applied after the last visible element, so a trailing gap can't appear at the bottom of a page or panel.
MarginLeft optional int 0 Space reserved to the element's left, in unscaled sprite pixels × Scale. This narrows the width the element measures against, so text wraps at the indented width rather than overflowing.
MarginRight optional int 0 Space reserved to the element's right, in unscaled sprite pixels × Scale.
Position optional Point { X: 0, Y: 0 } The element's position, in screen pixels. Only used in Background, Foreground, Underlay and Overlay, where elements are placed rather than stacked. Measured from wherever Alignment and VerticalAlignment anchor the element, so under the default Left and Top it's a plain coordinate (see Placed elements). Unlike every other spacing field this is not multiplied by Scale. Changing an element's scale resizes it in place rather than moving it.
Condition optional string A game state query. When it evaluates false the element is hidden, and elements below it close the gap. Re-checked several times a second while the book is open. Understands tokens.
Lifetime optional number Seconds the element stays up once shown. Setting it makes the element a timed one: hidden until a ShowElement action names it, so an Id is required.
FadeAfter optional number Seconds before it starts fading, reaching nothing at the end of Lifetime. Needs Lifetime, and must be below it.
IgnoreCursor optional bool false Lets the cursor pass straight through the element to whatever sits beneath it, so it can't be hovered or clicked. Its children and its own Background and Foreground are still reachable (see Passing the cursor through).
Action optional string A trigger action run when the element is clicked. When set, the element becomes interactive: any element type can have one, not just Button. Understands tokens.
Actions optional list of string Trigger actions run in order when the element is clicked. Combined with Action rather than replacing it, so an element with both runs Action first. Understands tokens.
Sound optional string bigSelect The cue played when the element is clicked. Only used when Action or Actions is set, and played once however many actions run. Set to null for a silent click.
HoverAction optional string A trigger action run when the cursor moves onto the element. Runs on entry, not while the cursor rests there. Sound doesn't apply. Understands tokens.
HoverActions optional list of string Trigger actions run in order when the cursor moves onto the element. Combined with HoverAction rather than replacing it, so an element with both runs HoverAction first. Understands tokens.
DisplayName optional string The bold title of the element's hover tooltip. Works in every list, including a page's Background and Foreground. Can carry tokens.
Description optional string The body of the element's hover tooltip. Works in every list, including a page's Background and Foreground, where setting it is also what makes an otherwise decorative element reachable by the cursor. Ignored when IgnoreCursor is set, which logs a warning. Can carry tokens.
Tags optional list of string Free-form markers other mods can read off whatever the cursor is over, such as Lookup Anything support. Tagging an element also makes it reachable by the cursor, since a tag is only useful on something that can be hovered. Ignored when IgnoreCursor is set. See Tags.
ParseTokenizableStrings optional bool true Whether the game's [Token] tokenizable strings are resolved in this element's Text, DisplayName, Description, Condition and actions. Set it to false where a square bracket is meant as the character itself.

Sprite fields

A Grid can carry a nine-sliced frame behind its cells exactly as a Panel does, and it's optional in the same way. Leave TexturePath out and only the cells draw.

Property Type Default Description
TexturePath optional string The asset name of the sprite sheet, such as Assets/PeacefulEnd.Parchment/panelFrame2 or a vanilla path like LooseSprites/Cursors. This is a game asset name, not a file path in your content pack. Load your PNG into the game's content with a Content Patcher Load patch first. See Loading your art.
TextureSourceRectangle optional Rectangle the whole texture The area of the sprite sheet to draw. Almost always needed: without it, the whole sheet is used, which for a multi-sprite sheet means a much larger element than you expect.
HoverTextureSourceRectangle optional Rectangle? An alternate source rectangle drawn while the cursor is over the element. Must be the same size as TextureSourceRectangle. The element's layout is measured from the normal rectangle, so a differently-sized hover sprite will overhang its own bounds. For a moving hover state on an Image, use HoverFrames instead.
TintColor optional color white A color multiplied into the sprite. Multiplying means it can only darken: red on grey art gives red, red on blue art gives near-black. Best on neutral or greyscale art.
SpriteEffects optional None | FlipHorizontally | FlipVertically None Mirrors the sprite within its own bounds. It does not move the element. If flipping appears to shift the art sideways, the sprite has uneven transparent padding inside its source rectangle. Combine with a comma: "FlipHorizontally, FlipVertically". Ignored by Panel and Button, which are nine-sliced.