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:
| Id | Name | Category |
|---|---|---|
| 1 | Apples | fruits |
| 2 | Bananas | fruits |
| 3 | Carrots | vegetables |
| 4 | Spinach | vegetables |
| 5 | Milk | dairy |
| 6 | Cheese | dairy |
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:truewhen this is the first item in thedataarray.$isLast:truewhen this is the last item in thedataarray.$item: The current data item.$itemIndex: The zero-based index of the current item in the fulldataarray.$selected:truewhen 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:
| Behavior | Properties |
|---|---|
| Animation | animation, animationOptions |
| Bookmark | bookmark, bookmarkLevel, bookmarkTitle, bookmarkOmitFromToc |
| Component Label | label, labelPosition, labelWidth, labelBreak, required, enabled, shrinkToLabel, style, readOnly |
| Publish/Subscribe | subscribeToTopic |
| Tooltip | tooltip, tooltipMarkdown, tooltipOptions |
| Styling Variant | variant |
Properties
checkboxPosition
default: "topStart"
Controls the position of the per-tile selection checkbox relative to the tile, respecting the current writing direction.
Available values:
| Value | Description |
|---|---|
topStart | Top of the tile, writing-direction start edge. (default) |
topEnd | Top of the tile, writing-direction end edge. |
bottomStart | Bottom of the tile, writing-direction start edge. |
bottomEnd | Bottom 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>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>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>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>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>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>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:
| Value | Description |
|---|---|
auto | Default text selection behavior |
text | Text can be selected by the user |
none | Text cannot be selected (default) |
contain | Selection is contained within this element |
all | The 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>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>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><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:
syncWithVarworks with both global and local variables. When using local variables, ensure all TileGrids in the sync have that variable in scope.
<App global.selState="{{}}">
<MyGrid />
<Text>Selection: {JSON.stringify(selState)}</Text>
<MyGrid />
</App><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.
<App global.selState="{{}}">
<MyGrid />
<Text>Selection: {JSON.stringify(selState)}</Text>
<MyGrid />
</App><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><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>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><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>Exposed Methods
This component does not expose any methods.
Styling
Theme Variables
| Variable | Default Value (Light) | Default Value (Dark) |
|---|---|---|
| backgroundColor-item-TileGrid | transparent | transparent |
| 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-TileGrid | 4px | 4px |
| offsetVertical-checkbox-TileGrid | 4px | 4px |
| outlineColor-item-TileGrid--focus | $color-primary-500 | $color-primary-500 |
| outlineOffset-item-TileGrid--focus | -2px | -2px |
| outlineStyle-item-TileGrid--focus | solid | solid |
| outlineWidth-item-TileGrid--focus | 2px | 2px |
| userSelect-item-TileGrid | none | none |