TileGrid

TileGrid renders a data array as a responsive, virtualized tile grid. It auto-calculates the number of columns based on the container width, tile width, and gap, and only renders visible rows — making it suitable for large datasets.

Key features:

  • Responsive layout: Automatically calculates the number of columns from available width, tile width, and gap
  • Virtualization: Only renders visible rows for smooth performance with large datasets
  • Tile selection: Optional single or multi-tile selection with per-tile checkboxes
  • Keyboard shortcuts: Built-in Ctrl/Cmd+A (select all), Delete, Cut, Copy, and Paste key bindings
  • State sync: Keep selection synchronized with a global variable via syncWithVar

Use TileGrid as a direct child of a container that provides an explicit height to enable virtualization.

In the following sections the examples use data with the structure outlined below:

IdNameCategory
1Applesfruits
2Bananasfruits
3Carrotsvegetables
4Spinachvegetables
5Milkdairy
6Cheesedairy

The data is provided inline. In source code samples, the data="{[...]}" declaration represents the data above.

All samples use a tile template with the following definition unless noted otherwise (the ... nested into <TileGrid> represents this template definition):

<TileGrid data="{[...]}" itemWidth="120px" itemHeight="80px">
  <VStack padding="8px" horizontalAlignment="center" verticalAlignment="center">
    <Text fontWeight="bold">{$item.name}</Text>
    <Text color="gray">{$item.category}</Text>
  </VStack>
</TileGrid>

Context variables available during execution:

  • $isFirst: true when this is the first item in the data array.
  • $isLast: true when this is the last item in the data array.
  • $item: The current data item.
  • $itemIndex: The zero-based index of the current item in the full data array.
  • $selected: true when this tile is currently selected.

Use children as Content Template

The itemTemplate property can be replaced by setting the item template component directly as the TileGrid's child. In the following example, the two TileGrid are functionally the same:

<App>
  <!-- This is the same -->
  <TileGrid>
    <property name="itemTemplate">
      <Text>Template</Text>
    </property>
  </TileGrid>
  <!-- As this -->
  <TileGrid>
    <Text>Template</Text>
  </TileGrid>
</App>

Behaviors

This component supports the following behaviors:

BehaviorProperties
Animationanimation, animationOptions
Bookmarkbookmark, bookmarkLevel, bookmarkTitle, bookmarkOmitFromToc
Component Labellabel, labelPosition, labelWidth, labelBreak, required, enabled, shrinkToLabel, style, readOnly
Publish/SubscribesubscribeToTopic
Tooltiptooltip, tooltipMarkdown, tooltipOptions
Styling Variantvariant

Properties

checkboxPosition

default: "topStart"

Controls the position of the per-tile selection checkbox relative to the tile, respecting the current writing direction.

Available values:

ValueDescription
topStartTop of the tile, writing-direction start edge. (default)
topEndTop of the tile, writing-direction end edge.
bottomStartBottom of the tile, writing-direction start edge.
bottomEndBottom of the tile, writing-direction end edge.

Controls where the per-tile selection checkbox is placed relative to the tile corner. The position respects the current writing direction (inset-inline-start / inset-inline-end).

Available values: topStart (default), topEnd, bottomStart, bottomEnd.

<App>
  <TileGrid
    data="{[...]}"
    itemWidth="120px"
    itemHeight="80px"
    itemsSelectable="true"
    checkboxPosition="bottomEnd"
  >
    <VStack padding="8px" horizontalAlignment="center" verticalAlignment="center">
      <Text fontWeight="bold">{$item.name}</Text>
    </VStack>
  </TileGrid>
</App>
Example: checkboxPosition

data

The array of items to render as tiles. Each item is exposed as $item inside the tile template.

The data property accepts a static array of objects or a URL that resolves to a JSON array. Each element of the array becomes a tile rendered by the itemTemplate.

<App>
  <TileGrid
    data="{[
      {id: 1, name: 'Apples'},
      {id: 2, name: 'Bananas'},
      {id: 3, name: 'Carrots'}
    ]}"
    itemWidth="120px"
    itemHeight="80px"
  >
    <Text fontWeight="bold">{$item.name}</Text>
  </TileGrid>
</App>
Example: data

enableMultiSelection

default: true

When true (default), multiple tiles can be selected using Ctrl/Cmd+Click or Shift+Click. When false, only a single tile can be selected at a time.

Controls whether multiple tiles can be selected simultaneously. When false, clicking a tile deselects any previously selected tile. This property only has an effect when itemsSelectable is true.

<App>
  <TileGrid
    data="{[...]}"
    itemWidth="120px"
    itemHeight="80px"
    itemsSelectable="true"
    enableMultiSelection="false"
  >
    <VStack padding="8px" horizontalAlignment="center" verticalAlignment="center">
      <Text fontWeight="bold">{$item.name}</Text>
    </VStack>
  </TileGrid>
</App>
Example: enableMultiSelection

gap

default: "8px"

Gap between tiles, e.g. "8px" or a theme variable like "$gap-normal".

Specifies the space between tiles both horizontally and vertically. Accepts any valid CSS length (e.g. "8px") or a theme variable (e.g. "$gap-normal").

<App>
  <TileGrid data="{[...]}" itemWidth="120px" itemHeight="80px" gap="16px">
    <VStack padding="8px" horizontalAlignment="center" verticalAlignment="center">
      <Text fontWeight="bold">{$item.name}</Text>
      <Text color="gray">{$item.category}</Text>
    </VStack>
  </TileGrid>
</App>
Example: gap

idKey

default: "id"

The property name used as the unique identifier for each item. Used to track selection state.

The property name used as the unique identifier for each data item. Used to track and restore selection state. Defaults to "id".

<App>
  <TileGrid
    idKey="key"
    itemsSelectable="true"
    data="{[
      {key: 'a', name: 'Alpha'},
      {key: 'b', name: 'Beta'},
      {key: 'c', name: 'Gamma'}
    ]}"
    itemWidth="120px"
    itemHeight="80px"
  >
    <Text fontWeight="bold">{$item.name}</Text>
  </TileGrid>
</App>

itemHeight

default: "140px"

Fixed height of each tile, e.g. "140px".

Specifies the fixed height of each tile as a CSS size value (e.g. "140px"). This value is also used as the virtualized row height, so it must be an explicit pixel value.

<App>
  <TileGrid data="{[...]}" itemWidth="120px" itemHeight="120px">
    <VStack padding="8px" horizontalAlignment="center" verticalAlignment="center">
      <Text fontWeight="bold">{$item.name}</Text>
      <Text color="gray">{$item.category}</Text>
    </VStack>
  </TileGrid>
</App>
Example: itemHeight

itemsSelectable

default: false

Enables selection mode. When true, tiles can be selected by clicking on them.

Enables selection mode. When true, each tile shows a checkbox and can be selected by clicking. The $selected context variable reflects the tile's selection state inside the template.

<App>
  <TileGrid data="{[...]}" itemWidth="120px" itemHeight="80px" itemsSelectable="true">
    <VStack
      padding="8px"
      horizontalAlignment="center"
      verticalAlignment="center"
      backgroundColor="{$selected ? '$color-primary-100' : 'transparent'}"
    >
      <Text fontWeight="bold">{$item.name}</Text>
    </VStack>
  </TileGrid>
</App>
Example: itemsSelectable

itemTemplate

The template used to render each tile. Use the $item context variable to access the current data item, $itemIndex for the zero-based index, and $selected to respond to the tile's selection state.

itemUserSelect

default: "none"

This property controls whether users can select text within tiles.

Available values:

ValueDescription
autoDefault text selection behavior
textText can be selected by the user
noneText cannot be selected (default)
containSelection is contained within this element
allThe entire element content is selected as one unit

Controls whether users can select text within tiles.

Available values: auto (default text selection behavior), text (text can be selected), none (text cannot be selected), contain (selection is contained within this element), all (entire element content is selected as one unit).

<App>
  <TileGrid
    data="{[...]}"
    itemWidth="120px"
    itemHeight="80px"
    itemUserSelect="text"
  >
    <VStack padding="8px" horizontalAlignment="center" verticalAlignment="center">
      <Text fontWeight="bold">{$item.name}</Text>
      <Text color="gray">{$item.category}</Text>
    </VStack>
  </TileGrid>
</App>
Example: itemUserSelect

itemWidth

default: "120px"

Fixed width of each tile, e.g. "120px".

Specifies the fixed width of each tile as a CSS size value (e.g. "120px"). The number of columns is derived from this value, the available container width, and the gap.

<App>
  <TileGrid data="{[...]}" itemWidth="160px" itemHeight="80px">
    <VStack padding="8px" horizontalAlignment="center" verticalAlignment="center">
      <Text fontWeight="bold">{$item.name}</Text>
      <Text color="gray">{$item.category}</Text>
    </VStack>
  </TileGrid>
</App>
Example: itemWidth

loading

default: false

When true, the grid shows a placeholder loading state instead of tile content.

When true, the grid hides all tile content and shows an empty placeholder. Use this while data is being fetched to prevent a layout flash.

<App>
  <TileGrid
    loading="true"
    itemWidth="120px"
    itemHeight="80px"
  >
    <Text>{$item.name}</Text>
  </TileGrid>
</App>
Example: loading
<App>
  <TileGrid
    loading="true"
    itemWidth="120px"
    itemHeight="80px"
  >
    <Text>{$item.name}</Text>
  </TileGrid>
</App>

syncWithVar

The name of a global variable to synchronize the grid's selection state with. The named variable must reference an object; the grid will read from and write to its selectedIds property. A runtime error is signalled if the value is not a valid JavaScript variable name.

The following example demonstrates two TileGrid components sharing selection state through a global variable. Selecting a tile in either grid immediately reflects in the other:

syncWithVar works with both global and local variables. When using local variables, ensure all TileGrids in the sync have that variable in scope.

Main.xmlui
<App global.selState="{{}}">
  <MyGrid />
  <Text>Selection: {JSON.stringify(selState)}</Text>
  <MyGrid />
</App>
MyGrid.xmlui
<Component name="MyGrid">
  <TileGrid
    syncWithVar="selState"
    itemsSelectable="true"
    data="{[
      {id: 1, name: 'Apples'},
      {id: 2, name: 'Bananas'},
      {id: 3, name: 'Carrots'}
    ]}"
    itemWidth="120px"
    itemHeight="80px"
  >
    <VStack padding="8px" horizontalAlignment="center" verticalAlignment="center">
      <Text fontWeight="bold">{$item.name}</Text>
    </VStack>
  </TileGrid>
</Component>

Change the selection in one of the grids and check how it is synced.

Main.xmlui
<App global.selState="{{}}">
  <MyGrid />
  <Text>Selection: {JSON.stringify(selState)}</Text>
  <MyGrid />
</App>
MyGrid.xmlui
<Component name="MyGrid">
  <TileGrid
    syncWithVar="selState"
    itemsSelectable="true"
    data="{[
      {id: 1, name: 'Apples'},
      {id: 2, name: 'Bananas'},
      {id: 3, name: 'Carrots'}
    ]}"
    itemWidth="120px"
    itemHeight="80px"
  >
    <VStack padding="8px" horizontalAlignment="center" verticalAlignment="center">
      <Text fontWeight="bold">{$item.name}</Text>
    </VStack>
  </TileGrid>
</Component>

Change the selection in one of the grids and check how it is synced.

Events

copyAction

Fired when the user presses Ctrl/Cmd+C. Receives (focusedItem, selectedItems, selectedIds).

Fired when the user presses Ctrl/Cmd+C while the grid is focused and itemsSelectable is true. The handler receives (focusedItem, selectedItems, selectedIds).

(See the deleteAction event for the general pattern.)

cutAction

Fired when the user presses Ctrl/Cmd+X. Receives (focusedItem, selectedItems, selectedIds).

Fired when the user presses Ctrl/Cmd+X while the grid is focused and itemsSelectable is true. The handler receives (focusedItem, selectedItems, selectedIds).

(See the deleteAction event for the general pattern.)

deleteAction

Fired when the user presses the Delete or Backspace key. Receives (focusedItem, selectedItems, selectedIds).

Fired when the user presses Delete while the grid is focused and itemsSelectable is true. The handler receives (focusedItem, selectedItems, selectedIds). The component does not remove items automatically — the handler must implement the removal.

<App var.msg="">
  <Text>{msg}</Text>
  <TileGrid
    data="{[
      {id: 1, name: 'Apples'},
      {id: 2, name: 'Bananas'},
      {id: 3, name: 'Carrots'}
    ]}"
    itemWidth="120px"
    itemHeight="80px"
    itemsSelectable="true"
    onDeleteAction="(focused, items, ids) => msg = 'Delete pressed for: ' + ids.join(', ')"
  >
    <Text fontWeight="bold">{$item.name}</Text>
  </TileGrid>
</App>
Example: deleteAction
<App var.msg="">
  <Text>{msg}</Text>
  <TileGrid
    data="{[
      {id: 1, name: 'Apples'},
      {id: 2, name: 'Bananas'},
      {id: 3, name: 'Carrots'}
    ]}"
    itemWidth="120px"
    itemHeight="80px"
    itemsSelectable="true"
    onDeleteAction="(focused, items, ids) => msg = 'Delete pressed for: ' + ids.join(', ')"
  >
    <Text fontWeight="bold">{$item.name}</Text>
  </TileGrid>
</App>

itemDoubleClick

Fired when a tile is double-clicked. Receives the data item.

Fired when a tile is double-clicked. The handler receives the tile's data item.

<App var.last="">
  <Text>Last double-clicked: {last}</Text>
  <TileGrid
    data="{[...]}"
    itemWidth="120px"
    itemHeight="80px"
    onItemDoubleClick="(item) => last = item.name"
  >
    <Text fontWeight="bold">{$item.name}</Text>
  </TileGrid>
</App>
Example: itemDoubleClick

pasteAction

Fired when the user presses Ctrl/Cmd+V. Receives (focusedItem, selectedItems, selectedIds).

Fired when the user presses Ctrl/Cmd+V while the grid is focused and itemsSelectable is true. The handler receives (focusedItem, selectedItems, selectedIds).

(See the deleteAction event for the general pattern.)

selectAllAction

Fired when the user presses Ctrl/Cmd+A. Receives (selectedItems, selectedIds).

Fired when the user presses Ctrl/Cmd+A while the grid is focused and itemsSelectable is true. The component automatically selects all tiles before invoking this handler. The handler receives (selectedItems, selectedIds).

<App var.msg="">
  <Text>{msg}</Text>
  <TileGrid
    data="{[
      {id: 1, name: 'Apples'},
      {id: 2, name: 'Bananas'},
      {id: 3, name: 'Carrots'}
    ]}"
    itemWidth="120px"
    itemHeight="80px"
    itemsSelectable="true"
    onSelectAllAction="(items, ids) => msg = 'Selected all: ' + ids.join(', ')"
  >
    <Text fontWeight="bold">{$item.name}</Text>
  </TileGrid>
</App>
Example: selectAllAction
<App var.msg="">
  <Text>{msg}</Text>
  <TileGrid
    data="{[
      {id: 1, name: 'Apples'},
      {id: 2, name: 'Bananas'},
      {id: 3, name: 'Carrots'}
    ]}"
    itemWidth="120px"
    itemHeight="80px"
    itemsSelectable="true"
    onSelectAllAction="(items, ids) => msg = 'Selected all: ' + ids.join(', ')"
  >
    <Text fontWeight="bold">{$item.name}</Text>
  </TileGrid>
</App>

selectionDidChange

Fired when the selection changes. Receives the array of currently selected items.

Fired when the set of selected tiles changes. The handler receives an array of the currently selected data items.

<App var.selected="">
  <Text>Selected: {selected}</Text>
  <TileGrid
    data="{[...]}"
    itemWidth="120px"
    itemHeight="80px"
    itemsSelectable="true"
    onSelectionDidChange="(items) => selected = items.map(i => i.name).join(', ')"
  >
    <Text fontWeight="bold">{$item.name}</Text>
  </TileGrid>
</App>
Example: selectionDidChange

Exposed Methods

This component does not expose any methods.

Styling

Theme Variables

VariableDefault Value (Light)Default Value (Dark)
backgroundColor-item-TileGridtransparenttransparent
backgroundColor-item-TileGrid--hover$color-surface-100$color-surface-100
backgroundColor-item-TileGrid--selected$color-surface-100$color-surface-100
backgroundColor-item-TileGrid--selected--hover$color-primary-100$color-primary-100
borderRadius-item-TileGrid$borderRadius$borderRadius
offsetHorizontal-checkbox-TileGrid4px4px
offsetVertical-checkbox-TileGrid4px4px
outlineColor-item-TileGrid--focus$color-primary-500$color-primary-500
outlineOffset-item-TileGrid--focus-2px-2px
outlineStyle-item-TileGrid--focussolidsolid
outlineWidth-item-TileGrid--focus2px2px
userSelect-item-TileGridnonenone