Layering
Most boxes sit where layout puts them, side by side.
Positioning moves a box off that spot, so it can overlap others,
and z decides which of the overlapping boxes is drawn on top.
Relative positioning
position_relative with insets moves a box from where layout placed it,
without moving anything else: its siblings keep the space it would have taken.
Insets are measured in cells, and inset_top(2) moves the box down two rows.
@component
def relative() -> Div:
return Div(
style=row | align_children_start | gap(3),
children=[
Text(style=border, content="first"),
Text(
style=position_relative | inset_top(2) | inset_left(2) | border | border_heavy,
content="position_relative\ninset_top(2)\ninset_left(2)",
),
Text(style=border, content="third"),
],
)
Absolute positioning
position_absolute takes a box out of the flow:
its siblings are laid out as if it weren't there,
and its insets place it relative to its parent's padding box.
@component
def absolute() -> Div:
return Div(
style=col | border,
children=[
Text(content="first, in flow"),
Text(
style=position_absolute | inset_top(2) | inset_left(10) | border | border_heavy,
content="position_absolute\ninset_top(2)\ninset_left(10)",
),
Text(content="second, in flow"),
],
)
Placing a box at an edge or corner
The inset_* utilities position a box absolutely at one of nine places in its parent.
Their center axes leave the insets on that axis unset,
which leaves the box where its parent's alignment puts it,
so they center only in a parent that centers its children, as this one does.
Margins push the box away from where it was placed.
@component
def root() -> Div:
return Div(
style=col,
children=[
Div(
style=row | fill(1) | border | border_heavy | center_children,
children=[
Text(
style=position_absolute | inset_left(3) | inset_top(3),
content="position_absolute | inset_left(3) | inset_top(3)",
),
Text(
style=inset_center_center | margin_left(-2) | margin_top(-4),
content="inset_center_center | margin_left(-2) | margin_top(-4)",
),
Text(
style=inset_bottom_right | margin_bottom(4),
content="inset_bottom_right | margin_bottom(4)",
),
Text(
style=inset_top_left,
content="inset_top_left",
),
Text(
style=inset_top_center,
content="inset_top_center",
),
Text(
style=inset_top_right,
content="inset_top_right",
),
Text(
style=inset_center_left,
content="inset_center_left",
),
Text(
style=inset_center_center,
content="inset_center_center",
),
Text(
style=inset_center_right,
content="inset_center_right",
),
Text(
style=inset_bottom_left,
content="inset_bottom_left",
),
Text(
style=inset_bottom_center,
content="inset_bottom_center",
),
Text(
style=inset_bottom_right,
content="inset_bottom_right",
),
],
)
],
)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃inset_top_left inset_top_center inset_top_right┃
┃ ┃
┃ ┃
┃ position_absolute | inset_left(3) | inset_top(3) ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ inset_center_center | margin_left(-2) | margin_top(-4) ┃
┃ ┃
┃inset_center_left inset_center_center inset_center_right┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ inset_bottom_right | margin_bottom(4)┃
┃ ┃
┃ ┃
┃ ┃
┃inset_bottom_left inset_bottom_center inset_bottom_right┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Controlling overlap with z
Where boxes overlap, the one with the higher z is drawn on top.
Boxes with equal z are drawn in the order they appear.
@component
def root() -> Div:
return Div(
style=col,
children=[
Text(
style=z(1)
| position_absolute
| inset_left(6)
| inset_top(6)
| border
| border_lightrounded
| margin(1)
| margin_color("purple", 600),
content="z = +1",
),
Text(
style=z(0)
| position_absolute
| inset_left(4)
| inset_top(3)
| border
| border_lightrounded
| margin(1)
| margin_color("teal", 600),
content="z = 0",
),
Text(
style=z(-1)
| position_absolute
| inset_left(0)
| inset_top(0)
| border
| border_lightrounded
| margin(1)
| margin_color("red", 600),
content="z = -1",
),
Text(
style=z(2)
| position_absolute
| inset_left(13)
| inset_top(3)
| border
| border_lightrounded
| margin(1)
| margin_color("amber", 600),
content="z = +2",
),
],
)
A dialog centered over the app
To draw a box over the rest of the app, put it inside an absolutely positioned overlay
whose insets of 0 stretch it over its whole parent,
and let the overlay center the box with ordinary alignment.
The overlay has no border, padding or margin, so it draws nothing itself,
and the app's content shows around the dialog.
The dialog is drawn over the content because it comes after it;
a dialog that comes earlier in the tree needs a higher z.
overlay = position_absolute | inset_top(0) | inset_bottom(0) | inset_left(0) | inset_right(0) | center_children
@component
def dialog() -> Div:
return Div(
style=col,
children=[
Text(style=border, content="header"),
Text(
style=fill(1) | border | text_wrap_stable,
content="The app's content stays where it is, and the dialog draws over it. " * 4,
),
Div(
style=overlay,
children=[
Text(
style=border | border_double | pad_x(2) | pad_y(1),
content="a dialog centered over the app",
),
],
),
],
)
┌──────────────────────────────────────────────────────────┐
│header │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│The app's c╔══════════════════════════════════╗alog draws │
│over it. Th║ ║nd the │
│dialog draw║ a dialog centered over the app ║here it is,│
│and the dia║ ║ stays │
│where it is╚══════════════════════════════════╝ │
│ │
│ │
└──────────────────────────────────────────────────────────┘
A badge pinned to a corner
Negative insets reach past the parent's padding box onto its border.
inset_top(-1) puts a box on the top border, and inset_right(-1) on the right border.
@component
def badge() -> Div:
return Div(
style=row | align_children_start | gap(2),
children=[
Div(
style=border | pad_x(1),
children=[
Text(content="inbox"),
Text(
style=position_absolute | inset_top(-1) | inset_right(1) | text_color("red", 500),
content="3",
),
],
),
Div(
style=border | pad_x(1),
children=[
Text(content="alerts"),
Text(
style=position_absolute | inset_top(-1) | inset_right(-1) | text_bg("red", 600),
content="!",
),
],
),
],
)