How-To: Layout

The Layout Components article explains the fundamentals of Stack and FlowLayout (and their derivatives), which are the primary layout components in XMLUI.

In this article, you will learn more subtle details to help you establish well-controlled layouts.

Setting vertical alignment

💡

A stack can align its contents vertically to the start (the default alignment), the end (bottom), or the center. However, these alignments seem to be ignored if the stack does not have an explicit size!

In this case, the stack's height is the same as its contents, so all vertical alignments (start, end, and center) will display the child component as it is. The following sample demonstrates this:

<App backgroundColor="lightgray">
  <VStack verticalAlignment="center" backgroundColor="cyan">
    This is some text within a Stack
  </VStack>
</App>

Though you expect the text to be aligned vertically in the middle of the app, it is not: the stack's height is the height of the text.

With an explicit size, you will see the effect of verticalAlignment:

<App backgroundColor="lightgray">
  <VStack verticalAlignment="center" height="100px" backgroundColor="cyan">
    This is some text within a Stack
  </VStack>
</App>

Setting vertical alignment with nested components

💡

When you nest components into each other, the vertical alignment may produce surprising effects. Though you expect something else, they produce the effect because you may forget explicit sizing.

Here is an example using a Table. The unexpected result is when you click Clear data, as the "Nothing here!" text appears at the top of the table's placeholder, though you use this definition for noDataTemplate:

<property name="noDataTemplate">
  <VStack height="100%" verticalAlignment="center" horizontalAlignment="center">
    Nothing here!
  </VStack>
</property>

Click the Clear data (and the Reload data) buttons to check the issue:

When you look at the markup, you can see that the outermost VStack has an explicit height, set to "100%":

<App
  backgroundColor="cyan"
  height="100%"
  var.tableData='{[]}'
  var.savedData='{${JSON.stringify(data)}}' >
  <HStack gap="0.5rem">
    <Button onClick="tableData = []">Clear data</Button>
    <Button onClick="tableData = savedData">Reload data</Button>
  </HStack>
  <Table data='{tableData}' backgroundColor="lightgreen">
    <Column bindTo="name"/>
    <Column bindTo="quantity"/>
    <Column bindTo="unit"/>
    <property name="noDataTemplate">
      <VStack height="100%" verticalAlignment="center" horizontalAlignment="center">
        Nothing here!
      </VStack>
    </property>
  </Table>
</App>

The cause of the issue is that the noDataTemplate is within the Table component. A Table is a layout container, too; it arranges its children (data rows). Vertical content alignment works the same way as described earlier. If you do not set the table height explicitly, the height of the noDataTemplate container is based on the contents. When the table displays the empty content template, the height is the line height of the "Nothing here!" text.

You can quickly fix this issue by specifying the table height explicitly:

<Table data='{tableData}' height="*" backgroundColor="lightgreen">
  <!-- Unchanged -->
</Table>

Check this out; now the noDataTemplate is rendered as expected:

📔

The "*" is start sizing. It means that the container should stretch itself to its parent's remaining (available) size. Using 100% would mean the entire parent (VStack) height and not just the available.

Desktop-like layout with resizable panels

You can easily create complex layouts with a few layout containers within App, as the following sample demonstrates:

<App height="100%" scrollWholePage="false">
  <Theme thickness-resizer-Splitter="4px" backgroundColor-resizer-Splitter="blue">
    <VStack height="100%" gap="0">
      <!-- Custom header -->
      <HStack
        height="$space-10"
        paddingHorizontal="$space-4"
        verticalAlignment="center"
        backgroundColor="lightblue">
        MenuBar
        <SpaceFiller />
        <Icon name="close"/>
      </HStack>
      <!-- Main content -->
      <HSplitter
        height="*"
        floating="true"
        initialPrimarySize="25%"
        minPrimarySize="10%"
        maxPrimarySize="90%">
        <VStack height="100%" backgroundColor="cyan">
          <H3>Left panel</H3>
        </VStack>
        <VSplitter
          height="100%"
          floating="true"
          initialPrimarySize="20%"
          minPrimarySize="10%"
          maxPrimarySize="90%">
          <VStack height="100%" backgroundColor="lightcoral">
            <H3>Upper right panel</H3>
          </VStack>
          <VStack height="100%" backgroundColor="lightyellow">
            <H3>Lower right panel</H3>
          </VStack>
        </VSplitter>
      </HSplitter>
      <!-- Custom footer -->
      <HStack
        height="$space-10"
        paddingHorizontal="$space-4"
        verticalAlignment="center"
        backgroundColor="lightblue">
        StatusBar
      </HStack>
    </VStack>
  </Theme>
</App>

Try the example! When you move the mouse to the boundary between the panels, you can drag the splitters to resize them.