Layout

An XMLUI app is a hierarchical component tree where parent components nest their children. While displaying children, each component arranges them with a particular strategy. For example, it puts children from in the same row horizontally, providing some gap among them.

Some components' only role is to arrange their nested children in a particular layout. We call them layout components. They can nest other layout components in arbitrary depths. This arrangement allows the creation of complex app and component layouts.

In this article, you will learn the basics of the XMLUI layout system and get acquainted with the fundamental layout components.

Layout Terminology

We use a few terms to help us understand the topics quickly when discussing layout.

Viewport

💡

Each component has a rectangular UI patch for rendering its content (and nested children). This is the component's viewport. The component decides (according to its rendering strategy) how it places its contents into the viewport. It may fill that partially, stretch the content for the entire viewport, or even overflow it vertically and horizontally.

The following app contains two components, an App, and a Text:

<App>
  <Text>Hello from XMLUI</Text>
</App>

The borders mark the viewport boundaries of the components:

  • App: The dotted red border is the app's viewport boundary. An App has the entire browser window as its viewport; however, it reserves some space to the left and right for scrollbars (to avoid viewport resizing when a vertical scrollbar appears or gets removed).
  • Text: The dotted green border is the text's viewport boundary. Its parent, App, uses some padding around its children.

Orientation

💡

When rendering its children, a component may render them with vertical or horizontal orientation.

  • Vertical orientation: Each child enters a new row when its parent displays it.
  • Horizontal orientation: Each child gets to the same row as its previous sibling. The component can decide when to enter a child component into a new row. For example, when the child does not fit into the remaining part of the row, the parent may enter it into a new row.

App uses vertical orientation, while HStack (horizontal stack) applies horizontal orientation.

<App>
  <Text>First item</Text>
  <HStack>
    <Text>Second item</Text>
    <Text>Third item</Text>
    <Text>Fourth item</Text>
  </HStack>
  <Text>Fifth item</Text>
</App>

Direction

💡

Some languages (such as Hebrew and Arabic) are read from right to left. XMLUI components use this information to change their children's rendering direction.

This example shows what happens when the browser uses right-to-left direction:

<App direction="rtl">
  <Text>First item</Text>
  <HStack>
    <Text>Second item</Text>
    <Text>Third item</Text>
    <Text>Fourth item</Text>
  </HStack>
  <Text>Fifth item</Text>
</App>

Paddings and Gaps

💡

Each component may apply padding and constrain the viewport its children can use. They can also add gaps between adjacent children.

The following sample demonstrates it:

<App>
  <Text>First item</Text>
  <HStack>
    <Text>Second item</Text>
    <Text>Third item</Text>
    <Text>Fourth item</Text>
  </HStack>
  <Text>Fifth item</Text>
</App>
  • App applies vertical and horizontal padding, which is why the top left corner of the red border and the green border do not meet. It also adds gaps, which are the spaces between the green border areas.
  • HStack uses zero paddings; thus, the top-left corner of its green border and the first item's top-left corner (the purple border) meet. Similarly to App, HStack adds gaps, which are the spaces between the purple border areas.

Margins

💡

Most web and desktop UI frameworks use another concept of spacing (margins) when establishing the layout. XMLUI layout components do not use margins; they only use paddings and gaps.

💡

Using both margins and paddings complicates layout arrangements due to side effects (such as margin collapse applied by HTML). Though you can use margins when creating your components, use them as a last resort. For most layouts, paddings must be enough.

Dimensions

💡

Each component has a strategy for determining its contents' dimensions (height and width). You can change the component dimensions if this default strategy is unsuitable for your particular layout.

By default, the VStack component determines its dimensions according to its content. However, if we want to display a 40px high and 60px wide orange-red box with empty content, we must explicitly set dimensions (and background color), as the default strategy won't work.

<App>
  <VStack height="40px" width="60px" backgroundColor="orangered" />
</App>

Alignment

💡

Components can align their children in the viewport both vertically and horizontally.

The following sample demonstrates it:

The component with the red border aligns its children vertically to the start and horizontally to the end. The green-bordered component aligns its children vertically to the center and horizontally to the start.

📔

Later in this article, you will learn how to establish the markup for such a layout.

Fundamental Layout Containers

💡

XMLUI uses only two fundamental layout containers, Stack, and FlowLayout. All other container-like components (such as Card, List, and others) apply these to establish more sophisticated layout arrangements.

Stack is a layout container that uses a particular orientation (vertical or horizontal) to render its children in a single column or row. If the children do not fit into the viewport, they overflow. Stack has two specialized variants, HStack (horizontal stack) and VStack (vertical stack), the orientation of which is suggested by their names.

FlowLayout is a layout container that renders its children horizontally while they fit into the current row; otherwise, the child enters a new row. If the children do not fit into the viewport, they overflow.

📔

Your application markup must have a single root component. The browser window is an implicit VStack layout container with that root element as its single child.

There are two other components used frequently with these layout containers, SpaceFiller, and Splitter. In this article, you will learn how to use them.

Dimension Units

💡

Layout containers use a particular strategy to render their child components; they calculate their children's dimensions.

In specific layouts, you want to set a child's dimensions explicitly (and not determined by the content of the particular child). You can set one or more of these component properties to set a particular dimension: width, height, minWidth, minHeight, maxWidth, and maxHeight.

A child can declare one of these kind of values for a specific dimension:

  • No value. The layout container determines the default size of the child element according to its strategy.
  • Container-independent size value. All sizes except percentage (%) and star sizes (*) belong to this category. The container respects the child's requested size.
  • Percentage size. The container calculates the child's requested size as a percentage of the viewport's corresponding dimension.
  • Star size. The child provides a weight the parent container utilizes when distributing the remaining space among its children. The remaining space is the parent viewport's size minus the sum sizes of child components within the first two categories (no value, container-independent size value).
📔

The article includes examples of these dimension value categories in the sections discussing a particular layout container.

While rendering the child components within the parent's viewport, specific components may overflow the provided viewport size. The layout container's strategy determines how to display (or hide) the exceeding child components.

Gaps

All fundamental layout containers apply a default gap, ensuring that child components have some space between them.

The following sample shows how a HStack renders button children:

<App>
  <HStack>
    <Button>First button</Button>
    <Button>Second button</Button>
    <Button>Third button</Button>
  </HStack>
</App>

You can remove the gaps if you intend to omit them entirely:

<App>
  <HStack gap="0">
    <Button>First button</Button>
    <Button>Second button</Button>
    <Button>Third button</Button>
  </HStack>
</App>

XMLUI offers several predefined gap values. Instead of inline literals (such as "16px", "0.5rem", etc.), use these values, as they can be themed, and it ensures a consistent design. You can learn about them here.

The following sample demonstrated using them:

<App>
  <VStack>
    <HStack gap="$gap-tight">
      <Button>First button</Button>
      <Button>Second button</Button>
      <Button>Third button</Button>
    </HStack>
    <HStack gap="$gap-loose">
      <Button>First button</Button>
      <Button>Second button</Button>
      <Button>Third button</Button>
    </HStack>
  </VStack>
</App>

Rendering Children

All layout container renders their children in declaration order; they consider the current page direction (left-to-right or right-to-left).

📔

You should know a few additional layout-related things about reusable components.

Let's see a few samples to see how they work! The samples use the following code (replacing Some_Container) with a particular layout container:

<App>
  <Some_Container>
    <Stack height="20px" width="20%" backgroundColor="orangered" />
    <Stack height="20px" width="20%" backgroundColor="orangered" />
    <Stack height="20px" width="80%" backgroundColor="orangered" />
    <Stack height="20px" width="20%" backgroundColor="orangered" />
    <Stack height="20px" width="20%" backgroundColor="orangered" />
  </Some_Container>
</App>

This markup displays five boxes; the third is four times wider than the others.

Vertical stack: Each child takes a new row.

Horizontal stack: All children take a single row.

Observe that the content overflows the width of a single row, and the app displays a horizontal scrollbar.

FlowLayout: The component breaks a child into a new row when it does not fit into the remaining part of the row.

Container Height

💡

If you set an explicit height (with the height layout property), the layout container will use that height; otherwise, it accommodates its content (children) height.

Check these examples:

<VStack
  backgroundColor="cyan"
  horizontalAlignment="center"
  verticalAlignment="center">
  This is some text within a VStack
</VStack>
<VStack
  height="160px"
  backgroundColor="cyan"
  horizontalAlignment="center"
  verticalAlignment="center">
  This is some text within a VStack
</VStack>

Check how the stack height changed (the cyan background) between the two examples!

When you explicitly set the height of a layout container, and the content is taller, that will overflow from the container:

<VStack height="40px" backgroundColor="cyan">
  <Text fontSize="3rem">This is some text within a Stack</Text>
</VStack>

Container Width

💡

Unless you use an explicit width, a layout container uses the entire width of its viewport.

Check these examples:

<VStack
  backgroundColor="cyan"
  horizontalAlignment="center"
  verticalAlignment="center">
  This is some text within a VStack
</VStack>
<VStack
  width="400px"
  backgroundColor="cyan"
  horizontalAlignment="center"
  verticalAlignment="center">
  This is some text within a VStack
</VStack>

Check how the stack width changed (the cyan background) between the two examples!

When you explicitly set the width of a layout container, and the content is wider, that will either break or overflow from the container.

For example, when you use text, the content can be broken into multiple lines, like in the following example:

<VStack width="400px" backgroundColor="cyan">
  <Text fontSize="2rem">This is some text within a Stack</Text>
</VStack>

Other components, such as a box, may overflow horizontally:

<VStack width="300px" backgroundColor="cyan">
  <HStack height="40px" border="2px solid red" width="400px"/>
</VStack>

Stack

The Stack component is an essential layout container. It renders its child items horizontally or vertically according to its orientation property, optionally providing some gap between child components.

You can assign the horizontal or vertical values to the Stack component's orientation property to declare its rendering orientation. The default value is vertical.

📔

Use the Stack component when its orientation property comes from an expression evaluated run time. If the orientation is static (it does not change run time), use VStack (equivalent with <Stack orientation="vertical">) and HStack (<Stack orientation="horizontal">). This style is straightforward and concise.

Content Alignment

💡

With the horizontalAlignment and verticalAlignment properties, you can define the corresponding alignment of children within a stack.

You can find more information about the values of horizontalAlignment and verticalAlignment here.

Here are a few samples. The following aligns the children of an HStack horizontally in the center:

<HStack horizontalAlignment="center">
  <Stack backgroundColor="red" height="36px" width="36px" />
  <Stack backgroundColor="green" height="36px" width="36px" />
  <Stack backgroundColor="blue" height="36px" width="36px" />
</HStack>

This sample aligns the children of a VStack horizontally to the end (right edge):

<VStack  horizontalAlignment="end">
  <Stack backgroundColor="red" height="36px" width="36px" />
  <Stack backgroundColor="green" height="36px" width="36px" />
  <Stack backgroundColor="blue" height="36px" width="36px" />
</VStack>

This sample aligns the children of an HStack vertically in the center:

<HStack verticalAlignment="center">
  <Stack backgroundColor="red" height="36px" width="36px" />
  <Stack backgroundColor="green" height="72px" width="36px" />
  <Stack backgroundColor="blue" height="48px" width="36px" />
</HStack>

Reverse Child Order

💡

Stack has a property, reverse, which you can use to reverse the rendering order.

With this flag set, a HStack renders its items in the opposite reading order of the current (for example, instead of left-to-right, it uses right-to-left). VStack starts rendering the last items and moves toward the first.

See the following example for HStack:

<HStack reverse="true">
  <Stack backgroundColor="red" height="36px" width="36px" />
  <Stack backgroundColor="green" height="36px" width="36px" />
  <Stack backgroundColor="blue" height="36px" width="36px" />
</HStack>

This example uses a VStack:

 
```xmlui copy /reverse="true"/
<VStack reverse="true">
  <Stack backgroundColor="red" height="36px" width="36px" />
  <Stack backgroundColor="green" height="36px" width="36px" />
  <Stack backgroundColor="blue" height="36px" width="36px" />
</VStack>

VStack

💡

A VStack component displays each of its children in a new row. If a child has no explicit (or component-specific) width, the VStack stretches the component to the entire viewport width. VStack keeps the child components' heights intact.

Here is an example:

<VStack>
  <H2 backgroundColor="orangered">I'm a heading with colored background</H2>
  <Button>I'm a button</Button>
</VStack>

The H2 component has no explicit size, so its width is set to the width of the text content (as the background color indicates). Though the Button component has no explicit size, it has a component-specific one (according to its content), so it is not stretched horizontally. The button is taller than the VStack, so its height determines the VStack height, and the text height is stretched to that.

VStack with Percentage Height

💡

When you use a VStack child with percentage height, the effective height is calculated from the entire stack height.

Such a setup may cause overflow if the sum of percentages equals 100%, as the gaps between children are also included in the stack height. The following example demonstrates an overflow:

<VStack height="200px" border="4px dotted green">
  <Stack backgroundColor="cyan" height="50%" />
  <Stack backgroundColor="orangered" height="50%" />
</VStack>

When the stack does not apply gaps, there is no overflow:

<VStack gap="0" height="200px" border="4px dotted green">
  <Stack backgroundColor="cyan" height="50%" />
  <Stack backgroundColor="orangered" height="50%" />
</VStack>

VStack with Star Height

💡

When you use a VStack child height with star-sizing, the effective height is calculated from the remaining height of the entire stack after subtracting the heights of explicitly sized children and gaps.

Such a configuration will not cause overflow. Here is a sample:

<VStack height="240px" border="4px dotted green">
  <Stack backgroundColor="cyan" height="*" />
  <H3>I'm a heading</H3>
  <Stack backgroundColor="orangered" height="2*" />
</VStack>

HStack

💡

A HStack component displays each of its children in a single row. If a child has no explicit (or component-specific) width, the HStack fits the component width to its content. HStack sets the child components' heights to the stack's viewport height.

Here is an example:

<HStack>
  <H2 backgroundColor="orangered">I'm a heading with colored background</H2>
  <Button>I'm a button</Button>
</HStack>

The H2 component has no explicit size, so it's stretched to the viewport width (as the background color indicates). Though Button has no explicit size, it has a component-specific one (according to its content), so it is not stretched.

HStack with Percentage Width

💡

When you use a HStack child with percentage width, the effective width is calculated from the stack's viewport width.

Such a setup may cause horizontal overflow if the sum of percentages equals 100%, as the gaps between children are also included in the stack height. The following example demonstrates this effect:

<HStack border="4px dotted green" height="200px">
  <Stack backgroundColor="cyan" width="50%" />
  <Stack backgroundColor="orangered" width="50%" />
</HStack>

When the stack does not apply gaps, there is no overflow:

<HStack gap="0" border="4px dotted green" height="200px">
  <Stack backgroundColor="cyan" width="50%" />
  <Stack backgroundColor="orangered" width="50%" />
</HStack>

HStack with Star Width

💡

When you use a HStack child width with star-sizing, the effective height is calculated from the remaining width of the stack's viewport width after subtracting the widths of explicitly sized children and gaps.

Such a configuration will not cause overflow. Here is a sample:

<HStack height="60px" border="4px dotted green">
  <Stack backgroundColor="cyan" width="*" />
  <H3>I'm a heading</H3>
  <Stack backgroundColor="orangered" width="2*" />
</HStack>

Content Wrapping

💡

HStack has a wrapContent property. If you set it to true, the engine starts a new line (or column) when the subsequent child to render would overflow in the current line.

In the following example, the fourth child does not fit in the first line entirely, so it overflows:

<HStack>
  <Stack backgroundColor="red" height="36px" width="25%" />
  <Stack backgroundColor="green" height="36px" width="40%" />
  <Stack backgroundColor="blue" height="36px" width="20%" />
  <Stack backgroundColor="purple" height="36px" width="30%" />
</HStack>

By setting the wrapContent flag, the forth child gets into a new line:

<HStack wrapContent="true">
  <Stack backgroundColor="red" height="36px" width="25%" />
  <Stack backgroundColor="green" height="36px" width="40%" />
  <Stack backgroundColor="blue" height="36px" width="20%" />
  <Stack backgroundColor="purple" height="36px" width="30%" />
</HStack>

Rendering Direction

💡

The HStack component respects the reading direction set in the browser and renders its children accordingly.

The following example shows how a the right-to-left reading direction renders the Stack:

<HStack direction="rtl">
  <Stack backgroundColor="red" height="36px" width="36px" />
  <Stack backgroundColor="green" height="36px" width="36px" />
  <Stack backgroundColor="blue" height="36px" width="36px" />
</HStack>

The Stack has a property, reverse, which you can use to reverse the rendering order suggested by the current reading direction. While the direction value affects only the horizontal stack, the reverse value affects the vertical one, too.

<HStack padding="1rem" gap="1rem" reverse="true">
  <Stack backgroundColor="red" height="36px" width="36px" />
  <Stack backgroundColor="green" height="36px" width="36px" />
  <Stack backgroundColor="blue" height="36px" width="36px" />
</HStack>
<VStack padding="1rem" gap="1rem" reverse="true">
  <Stack backgroundColor="red" height="36px" width="36px" />
  <Stack backgroundColor="green" height="36px" width="36px" />
  <Stack backgroundColor="blue" height="36px" width="36px" />
</VStack>

Content Wrapping

The Stack has a wrapContent property to start a new line (or column) when the subsequent child to render would overflow.

In the following example, the fourth child does not fit in the first line entirely, so it overflows:

<HStack padding="1rem" gap="1rem">
  <Stack backgroundColor="red" height="36px" width="25%" />
  <Stack backgroundColor="green" height="36px" width="40%" />
  <Stack backgroundColor="blue" height="36px" width="20%" />
  <Stack backgroundColor="purple" height="36px" width="30%" />
</HStack>

By setting the wrapContent flag, the forth child gets into a new line:

<HStack padding="1rem" gap="1rem" wrapContent="true">
  <Stack backgroundColor="red" height="36px" width="25%" />
  <Stack backgroundColor="green" height="36px" width="40%" />
  <Stack backgroundColor="blue" height="36px" width="20%" />
  <Stack backgroundColor="purple" height="36px" width="30%" />
</HStack>

Note: Content wrapping is unavailable with the vertical stack.

CHStack

💡

CHStack is a shorthand version of Stack with a horizontal orientation with its contents centered.

<Stack
  orientation="horizontal"
  verticalAlignment="center"
  horizontalAlignment="center"
/>

Here is an example:

<CHStack height="100px" width="200px" backgroundColor="lightgray">
  <Stack backgroundColor="red" height="36px" width="36px" />
  <Stack backgroundColor="green" height="72px" width="36px" />
  <Stack backgroundColor="blue" height="48px" width="36px" />
</CHStack>

CVStack

💡

CVStack is a shorthand version of Stack with a vertical orientation with its contents centered.

<Stack
  orientation="vertical"
  verticalAlignment="center"
  horizontalAlignment="center"
/>

Here is an example:

<CVStack height="200px" width="100px" backgroundColor="lightgray">
  <Stack backgroundColor="red" height="36px" width="36px" />
  <Stack backgroundColor="green" height="36px" width="72px" />
  <Stack backgroundColor="blue" height="36px" width="48px" />
</CVStack>

FlowLayout

The FlowLayout component resembles a horizontal stack with content wrapping. Though it implements the same behavior, it has extra features:

  • Percentage sizing: FlowLayout considers the gaps between child elements when using percentage sizing, unlike Stack.
  • Responsiveness: FlowLayout resizes percentage-sized children on mobile devices.

Aligned Percentage Sizing

💡

When you use an HStack with percentage sizing and the sum width of children is 100%, an overflow will occur because gaps require extra space.

The following sample demonstrates such a situation:

<HStack>
  <Stack backgroundColor="red" height="36px" width="25%" />
  <Stack backgroundColor="green" height="36px" width="50%" />
  <Stack backgroundColor="blue" height="36px" width="25%" />
</HStack>
💡

The FlowLayout component handles this sizing issue by adjusting the child component dimensions accounting for the gaps.

<FlowLayout>
  <Stack backgroundColor="red" height="36px" width="25%" />
  <Stack backgroundColor="green" height="36px" width="50%" />
  <Stack backgroundColor="blue" height="36px" width="25%" />
</FlowLayout>

Size Capping

💡

The FlowLayout component caps the size of items exceeding the available width.

In the following sample, the red box is too wide. Nonetheless, the FlowLayout trims it back to 100% width:

<FlowLayout>
  <Stack backgroundColor="red" height="36px" width="1000000px" />
  <Stack backgroundColor="green" height="36px" width="50%" />
  <Stack backgroundColor="blue" height="36px" width="25%" />
</FlowLayout>

Note how the extreme width of the first child is capped to the space available for the FlowLayout, while the other children's sizes remain unmodified:

SpaceFiller

💡

SpaceFiller is a component that fills the remaining (unused) space in layout containers. Its behavior depends on the layout container in which it is used.

In a Stack, SpaceFiller pushes the children following it to the other end of the container:

<HStack>
  <Stack width="36px" height="36px" backgroundColor="red" />
  <SpaceFiller />
  <Stack width="36px" height="36px" backgroundColor="blue" />
</HStack>

In a FlowLayout, SpaceFiller acts as a line break for a row. The children following the SpaceFiller enters a new line.

<FlowLayout>
  <Stack width="20%" height="36px" backgroundColor="red" />
  <SpaceFiller />
  <Stack width="20%" height="36px" backgroundColor="green" />
  <Stack width="20%" height="36px" backgroundColor="blue" />
</FlowLayout>

Splitter

The Splitter component divides a container (such as a window, panel, pane, etc.) into two resizable sections (a primary and a secondary) and puts a draggable bar between them.

Splitter has two specialized variants, HSplitter and VSplitter, which separate the two sections vertically and horizontally.

The following example demonstrates a horizontal splitter, which sets some constraints on the size of the primary section:

<HSplitter
  height="100%"
  minPrimarySize="10%"
  maxPrimarySize="90%">
  <CVStack backgroundColor="lightblue" height="100%">Primary</CVStack>
  <CVStack backgroundColor="darksalmon" height="100%">Secondary</CVStack>
</HSplitter>

Try dragging the splitter bar between the sections to experience how it works.

See the Splitter reference documentation to learn more about this component.