CalmUI
Components

Conditions and Lists

ui:if, ui:show, ui:for, and ui:key control structure and identity.

Use these attributes in your component template to show or repeat content.

  • ui:if creates content when its condition is true and removes it when false. Removing focused content clears focus. A missing props.* path used as its condition evaluates as false, which allows optional content such as ui:if="props.description". The same missing path remains an error when used without this guard.
  • ui:show hides content when false while keeping its component state. Hidden content cannot receive clicks.
  • ui:for="item in props.items": repeat. Components need ui:key="item.id".
  • Keys are unique among siblings. Duplicate runtime keys are errors.

Never use a list index as ui:key.

Fixed-height virtual lists

A virtual list displays a small window of a large collection. Use it when all rows have the same height. You must supply the scroll offset yourself; it does not create a scrollbar. In the example below, import InventoryRow and provide items, scrollOffset, and the viewportHeight prop first.

<InventoryRow
  ui:for="item in items"
  ui:key="item.id"
  ui:virtual="fixed"
  ui:virtual-item-height="36"
  ui:virtual-viewport-height="props.viewportHeight"
  ui:virtual-scroll-offset="scrollOffset"
  ui:virtual-overscan="3"
  :item="item"
/>

The item height, viewport height, and scroll offset are logical pixels. Overscan is an optional non-negative item count and defaults to 2. Only the visible rows plus that extra margin are created and displayed. Overscan is the number of extra rows kept just outside the visible area. The full collection is still checked for unique stable keys when it changes.

Only fixed-height rows are supported. A value other than ui:virtual="fixed" is a compiler error. When inserting, removing, or reordering before the viewport, adjust scrollOffset by the row-height delta to preserve the anchor. When controller or keyboard navigation targets an item outside the window, update scrollOffset first so that the focused item is created and available for focus. TypeScript can use virtualListOffsetForIndex(config, index, alignment) and preserveVirtualListAnchor(...) from @calmui/runtime so its navigation and mutation math matches Core. Active keyed items retain component identity, local state, focus, hover/capture, and animation ownership while retained by the windowing runtime; deleting their domain key clears that state.

Materialized rows receive aria-posinset and aria-setsize; put role="list" on the container and role="listitem" on each row. The Studio Inspector shows the item's one-based position and total count, and Profiler shows materialized, total, and retained item counts.

On this page