Skip to content

Grids and Wrapping

display_grid lays a container's children out in rows and columns at once. The container lists its tracks with grid_template_columns and grid_template_rows, and each child fills the next free cell, or the cells it asks for. The examples on this page size every track with fr(1), one share of the grid's free space.

Dashboard tiles

Equal tracks on both axes give a grid of equal tiles, and gap separates them.

@component
def tiles() -> Div:
    return Div(
        style=display_grid | grid_template_columns(fr(1), fr(1), fr(1)) | grid_template_rows(fr(1), fr(1)) | gap(1),
        children=[Text(style=border, content=f"tile {n}") for n in range(1, 7)],
    )

Dashboard tiles

┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│tile 1           │ │tile 2            │ │tile 3           │
│                 │ │                  │ │                 │
│                 │ │                  │ │                 │
│                 │ │                  │ │                 │
└─────────────────┘ └──────────────────┘ └─────────────────┘

┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│tile 4           │ │tile 5            │ │tile 6           │
│                 │ │                  │ │                 │
│                 │ │                  │ │                 │
└─────────────────┘ └──────────────────┘ └─────────────────┘

Cells spanning tracks

grid_column and grid_row place a child by grid line, counted from 1, and span(n) stretches it across n tracks. Children without a placement fill the cells that are left, in order.

Place a spanning child on both axes, as both are here. A child placed on rows alone is placed before one placed on columns alone, whatever their order among the children, so mixing the two can push a child into an extra row below the template.

@component
def spans() -> Div:
    return Div(
        style=display_grid | grid_template_columns(fr(1), fr(1), fr(1)) | grid_template_rows(fr(1), fr(1), fr(1)),
        children=[
            Text(
                style=grid_row(1) | grid_column(1, span(2)) | border | border_heavy,
                content="grid_row(1)\ngrid_column(1, span(2))",
            ),
            Text(
                style=grid_row(1, span(3)) | grid_column(3) | border | border_heavy,
                content="grid_row(\n  1, span(3),\n)\ngrid_column(3)",
            ),
            Text(style=border, content="auto"),
            Text(style=border, content="auto"),
            Text(style=border, content="auto"),
            Text(style=border, content="auto"),
        ],
    )

Cells spanning tracks

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┏━━━━━━━━━━━━━━━━━━┓
┃grid_row(1)                           ┃┃grid_row(         ┃
┃grid_column(1, span(2))               ┃┃  1, span(3),     ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃)                 ┃
┌──────────────────┐┌──────────────────┐┃grid_column(3)    ┃
│auto              ││auto              │┃                  ┃
│                  ││                  │┃                  ┃
└──────────────────┘└──────────────────┘┃                  ┃
┌──────────────────┐┌──────────────────┐┃                  ┃
│auto              ││auto              │┃                  ┃
│                  ││                  │┃                  ┃
└──────────────────┘└──────────────────┘┗━━━━━━━━━━━━━━━━━━┛

Auto flow

Children beyond the template's cells get new tracks, which the grid adds as it needs them. By default, children fill each row in turn and the grid adds rows, like the seventh child on the left. grid_auto_flow_column fills each column in turn and adds columns instead.

@component
def auto_flow() -> Div:
    return Div(
        style=row | gap(2),
        children=[
            Div(
                style=display_grid | grid_template_columns(fr(1), fr(1), fr(1)) | fill(1) | border | border_heavy,
                children=[
                    Text(
                        content=" grid_template_columns(fr(1), fr(1), fr(1)) ",
                        style=position_absolute | inset_top(-1) | inset_left(1),
                    ),
                    *(Text(style=border, content=str(n)) for n in range(1, 8)),
                ],
            ),
            Div(
                style=display_grid
                | grid_template_rows(fr(1), fr(1), fr(1))
                | grid_auto_flow_column
                | fill(1)
                | border
                | border_heavy,
                children=[
                    Text(
                        content=" grid_template_rows(fr(1), fr(1), fr(1)) ",
                        style=position_absolute | inset_top(-1) | inset_left(1),
                    ),
                    Text(content=" grid_auto_flow_column ", style=position_absolute | inset_bottom(-1) | inset_left(1)),
                    *(Text(style=border, content=str(n)) for n in range(1, 8)),
                ],
            ),
        ],
    )

Row and column auto flow

┏━ grid_template_columns(fr(1), fr(1), fr(1)) ━━┓  ┏━ grid_template_rows(fr(1), fr(1), fr(1)) ━━━━━┓
┃┌──────────────┐┌─────────────┐┌──────────────┐┃  ┃┌──────────────┐┌─────────────┐┌──────────────┐┃
┃│1             ││2            ││3             │┃  ┃│1             ││4            ││7             │┃
┃└──────────────┘└─────────────┘└──────────────┘┃  ┃└──────────────┘└─────────────┘└──────────────┘┃
┃┌──────────────┐┌─────────────┐┌──────────────┐┃  ┃┌──────────────┐┌─────────────┐                ┃
┃│4             ││5            ││6             │┃  ┃│2             ││5            │                ┃
┃└──────────────┘└─────────────┘└──────────────┘┃  ┃└──────────────┘└─────────────┘                ┃
┃┌──────────────┐                               ┃  ┃┌──────────────┐┌─────────────┐                ┃
┃│7             │                               ┃  ┃│3             ││6            │                ┃
┃└──────────────┘                               ┃  ┃└──────────────┘└─────────────┘                ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛  ┗━ grid_auto_flow_column ━━━━━━━━━━━━━━━━━━━━━━━┛

Wrapping without a grid

When children have different sizes and there's no grid for them to line up in, flex_wrap lets a row start a new line when the next child doesn't fit. gap_width spaces the children within a line.

@component
def wrap() -> Div:
    return Div(
        style=row | flex_wrap | align_children_start | gap_width(1),
        children=[Text(style=border | pad_x(1), content=tag) for tag in TAGS],
    )

Wrapping with flexbox

┌────────┐ ┌─────────┐ ┌──────┐ ┌──────────┐      
│ layout │ │ flexbox │ │ grid │ │ wrapping │      
└────────┘ └─────────┘ └──────┘ └──────────┘      
┌───────────┐ ┌──────┐ ┌─────────┐ ┌───┐          
│ alignment │ │ text │ │ borders │ │ z │          
└───────────┘ └──────┘ └─────────┘ └───┘          
┌─────────────┐ ┌─────┐                           
│ positioning │ │ gap │                           
└─────────────┘ └─────┘