Back to flin
flin

The Layout System

How FlinUI's layout components -- Container, Stack, Grid, Flex, Split -- replace custom CSS with declarative, responsive layout primitives that compose naturally.

Thales & Claude | March 25, 2026 9 min flin
flinflinuilayoutcssgrid

CSS layout is powerful. It is also the single largest source of developer frustration on the web. Flexbox requires understanding main axis vs. cross axis, flex-grow vs. flex-shrink vs. flex-basis, and a dozen properties that interact in non-obvious ways. CSS Grid requires understanding grid-template-columns, grid-template-rows, grid-area, and the difference between fr, auto, and minmax(). Centering a div -- the canonical web development joke -- still requires knowing the right combination of display, align-items, and justify-content.

FlinUI's layout system replaces this complexity with 15 declarative components that handle the most common layout patterns. You do not write CSS. You compose components. for vertical and horizontal arrangements. for grid layouts. for max-width constraints.

for centering. Each component maps to CSS flexbox or grid under the hood, but the developer never sees the CSS.

Container: Constrained Width

Every page needs a maximum width. Without it, content stretches to the full viewport width, making text lines impossibly long on wide screens.

<Container>
    <h1>Default container (max-width: 1280px)</h1>
    <p>Content is centered and constrained</p>
</Container>

// max-width: 640px // max-width: 768px // max-width: 1024px // max-width: 1280px // max-width: 100% ```

Container applies max-width, margin: 0 auto (for centering), and horizontal padding. That is three CSS properties that every web page needs and that every developer writes slightly differently. Container standardizes them.

Stack: The Universal Arranger

Stack is the most-used layout component. It arranges children in a line -- vertically or horizontally -- with consistent spacing between them.

// Vertical stack (default)
<Stack gap={4}>
    <Text size="xl" weight="bold">Profile</Text>
    <Input label="Name" value={name} />
    <Input label="Email" value={email} />
    <Button variant="primary">Save</Button>
</Stack>

// Horizontal stack {user.name} Online ```

The gap prop uses the spacing token scale (gap={4} means space_4 = 16px). The direction prop is either "vertical" (default) or "horizontal". The align prop controls cross-axis alignment: "start", "center", "end", "stretch".

Under the hood, Stack renders a

with display: flex, flex-direction, gap, and align-items. But the developer never needs to know this.

Stack vs. Flexbox

What a developer writes in CSS:

.profile-form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.user-info { display: flex; flex-direction: row; gap: 0.5rem; align-items: center; } ```

What a developer writes in FLIN:

<Stack gap={4}>...</Stack>
<Stack direction="horizontal" gap={2} align="center">...</Stack>

Same result. No CSS file. No class names. No mental model of flex properties.

Grid: Two-Dimensional Layout

For layouts that need rows and columns, Grid provides a CSS Grid-based layout:

// Simple 3-column grid
<Grid cols={3} gap={4}>
    <Card>One</Card>
    <Card>Two</Card>
    <Card>Three</Card>
    <Card>Four</Card>
    <Card>Five</Card>
    <Card>Six</Card>
</Grid>

// Responsive grid {for product in products} {/for} ```

The responsive variant uses breakpoint-prefixed props. cols={1} is the mobile default (one column). cols_md={2} activates at the medium breakpoint (768px). cols_lg={3} at 1024px. cols_xl={4} at 1280px.

Grid handles the responsive CSS automatically:

/* Generated CSS (the developer never sees this) */
.grid {
    display: grid;
    grid-template-columns: repeat(1, 1fr);
    gap: 1rem;
}
@media (min-width: 768px) {
    .grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
    .grid { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1280px) {
    .grid { grid-template-columns: repeat(4, 1fr); }
}

Four lines of FLIN instead of twelve lines of CSS. And the FLIN version is readable without knowing CSS Grid syntax.

Flex: Fine-Grained Control

When Stack and Grid are not enough, Flex provides direct access to flexbox properties:

<Flex direction="row" wrap="wrap" justify="space-between" align="center">
    <Logo />
    <Nav />
    <UserMenu />
</Flex>

Flex exposes the full flexbox API through props: - direction: row, column, row-reverse, column-reverse - wrap: nowrap, wrap, wrap-reverse - justify: start, center, end, space-between, space-around, space-evenly - align: start, center, end, stretch, baseline - gap: spacing token value

Most layouts do not need Flex. Stack handles vertical and horizontal arrangements. Grid handles two-dimensional layouts. Flex is for the 10% of cases where you need justify-content: space-between or flex-wrap: wrap.

Split: Sidebar Layouts

The Split component creates a two-panel layout with a fixed ratio:

<Split ratio="1:3">
    <aside>Sidebar content</aside>
    <main>Main content</main>
</Split>

... ...

```

The ratio prop defines how space is divided between the two children. "1:3" means the first child gets 25% and the second gets 75%. "1:4" means 20% and 80%. "1:1" means 50/50.

Split handles the responsive collapse automatically: on mobile (below the medium breakpoint), the two panels stack vertically, each taking 100% width. On desktop, they sit side by side at the specified ratio.

Box: The Primitive

Box is the base layout component. It is a div with style props:

<Box padding={4} margin={2} bg="white" radius="md" shadow="sm">
    Content here
</Box>

Box accepts every spacing, color, and visual prop that other components use internally. It is the escape hatch for one-off layouts that do not fit the patterns of Stack, Grid, or Split.

Most developers rarely use Box directly. The higher-level components (Card, Alert, Badge) are boxes with specific visual treatment. Box exists for the cases where you need "a div with some padding and a shadow" without creating a new component.

Center: The Solved Problem

<Center>
    <Spinner size="lg" />
    <Text>Loading...</Text>
</Center>

```

Center centers its children both horizontally and vertically. It applies display: flex; align-items: center; justify-content: center -- the three properties that solve the "centering a div" problem. The optional height prop sets the container height (useful for full-viewport centering).

Divider and Spacer

// Horizontal divider
<Divider />
<Divider label="Or continue with" />

// Vertical divider in a horizontal stack

// Fixed-size spacer

// Flexible spacer (pushes content to edges) ```

Divider renders a visual separator line. It can be horizontal (default) or vertical, and it can contain a label (centered text on the line).

Spacer creates empty space. With a size prop, it adds fixed space. Without props, it grows to fill available space -- the flexbox flex-grow: 1 pattern that pushes siblings to opposite ends.

AspectRatio

<AspectRatio ratio="16:9">
    <Image src={video_thumbnail} />
</AspectRatio>

```

AspectRatio constrains its child to a specific aspect ratio. The ratio prop accepts common formats: "16:9", "4:3", "1:1", "21:9". This is essential for responsive images and video embeds that need to maintain their proportions as the container resizes.

Wrap

<Wrap gap={2}>
    {for tag in tags}
        <Tag>{tag}</Tag>
    {/for}
</Wrap>

Wrap arranges children in a row, wrapping to the next line when they exceed the container width. It is display: flex; flex-wrap: wrap with a gap. Perfect for tag lists, chip groups, and any collection of elements that should flow like text.

Composing Layout Components

The real power of the layout system emerges when components compose:

<Container size="xl">
    <Stack gap={8}>
        <!-- Page header -->
        <Stack direction="horizontal" align="center">
            <Stack gap={1}>
                <Text size="2xl" weight="bold">Dashboard</Text>
                <Text color="muted">Overview of your application</Text>
            </Stack>
            <Spacer />
            <Button variant="primary">Export</Button>
        </Stack>

Revenue Trend Recent Orders ```

This is a complete dashboard layout. Container constrains the width. Stack provides vertical spacing. Grid creates responsive columns. No CSS file. No class names. No media queries. The layout is expressed entirely through component composition, and it is responsive by default.

Why Components Instead of CSS Classes

Tailwind CSS takes the approach of utility classes:

. This is more concise than writing CSS, but it is still CSS-in-disguise -- the developer must know what flex, flex-col, gap-4, and mx-auto do.

FlinUI takes the approach of semantic components: , . The developer does not need to know CSS at all. Stack arranges things in a line. Grid makes a grid. Container constrains width. Center centers things. The component name is the intent.

For FLIN's target audience -- developers who want to build applications, not master CSS -- this is the right abstraction level. The layout system handles the "how" so the developer can focus on the "what."

Common Layout Patterns

The 15 layout components cover the patterns that appear in nearly every web application:

Holy Grail Layout (header, sidebar, content, footer): ``flin ... ...

...
``

Centered Form Page (login, registration, password reset): ``flin

...
``

Marketing Landing Page (full-width sections with constrained content): ``flin

Hero Title
``

Each pattern is composed from the same 15 layout components. No custom CSS. No memorizing flexbox properties. No debugging why margin: 0 auto does not work when you forgot display: block. Just components that do what their names say.

Implementation: CSS Generation

Each layout component generates the appropriate CSS. The Grid component, for example, generates a

with CSS Grid properties calculated from props:

fn render_grid(props: &Props) -> String {
    let cols = props.get_int("cols", 1);
    let gap = props.get_spacing("gap", 4);

let mut style = format!( "display: grid; grid-template-columns: repeat({}, 1fr); gap: {};", cols, gap );

// Add responsive overrides if let Some(cols_md) = props.get_opt_int("cols_md") { // Emitted as a scoped media query }

format!("

{}
", style, children) } ```

The layout components are among the simplest in FlinUI -- most are under 30 lines of FLIN code. Their power comes not from complexity but from composition: fifteen simple components that, combined, express any layout a web application needs.

---

This is Part 86 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO built a layout system that replaces CSS with declarative components.

Series Navigation: - [85] Design Tokens and Theming System - [86] The Layout System (you are here) - [87] Icons Library Integration - [88] FlinUI Enterprise Components

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles