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. AnApp
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 toApp
,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, unlikeStack
. - 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.