Skip to content

Pack

Usage

Toga's default style engine, Pack, is a layout algorithm based around the idea of packing boxes inside boxes. Each box specifies a direction for its children, and each child specifies how it will consume the available space - either as a specific width, or as a proportion of the available width. Other properties exist to control color, text alignment and so on.

It is similar in some ways to the CSS Flexbox algorithm; but dramatically simplified, as there is no allowance for overflowing boxes.

The string values defined here are the string literals that the Pack algorithm accepts. These values are also pre-defined as Python constants in the toga.style.pack module with the same name; however, following Python style, the constants use upper case and dashes are underscores. For example, the Python constant toga.style.pack.SANS_SERIF evaluates as the string literal "sans-serif". (The constant NONE, or "none", is distinct from Python's None.)

Some properties, despite always storing their value in a consistent form, are more liberal in what they accept, and will convert as necessary when assigned alternate forms. Where relevant, these are listed under Accepts.

Toga has a layout debug mode to aid in visually debugging or exploring Pack layouts.

Reference

Source code in core/src/toga/style/pack.py
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
@dataclass(kw_only=True, repr=False)
class Pack(PackLogic):
    _doc_link = "[style properties](/reference/style/pack)"

    display: str = validated_property(PACK, NONE, initial=PACK)
    """Defines how to display the widget.

    **Allowed values:** `"pack"` or `"none"`

    **Default value:** `"pack"`

    A value of `"pack"` will apply the pack layout algorithm to this node and its
    descendants. A value of `"none"` removes the widget from the layout entirely. Space
    will be allocated for the widget as if it were there, but the widget itself will
    not be visible.
    """
    visibility: str = validated_property(VISIBLE, HIDDEN, initial=VISIBLE)
    """Defines whether the widget should be drawn.

    **Allowed values:** `"hidden"` or `"visible"`

    **Default value:** `"visible"`

    A value of `"visible"` means the widget will be displayed. A value of `"hidden"`
    removes the widget from view, but allocates space for the widget as if it were
    still in the layout.

    Any children of a hidden widget are implicitly removed from view.

    If a previously hidden widget is made visible, any children of the widget with a
    visibility of `"hidden"` will remain hidden. Any descendants of the hidden child
    will also remain hidden, regardless of their visibility.
    """
    direction: str = validated_property(ROW, COLUMN, initial=ROW)
    """The packing direction for children of the box.

    **Allowed values:** `"row"` or `"column"`

    **Default value:** `"row"`

    A value of `"column"` indicates children will be stacked vertically, from top to
    bottom. A value of `"row"` indicates children will be packed horizontally;
    left-to-right if `text_direction` is `"ltr"`, or right-to-left if `text_direction`
    is `"rtl"`.
    """
    align_items: str | None = validated_property(START, CENTER, END)
    """The alignment of this box's children along the cross axis.

    **Allowed values:** `"start"`, `"center"`, or `"end"`

    **Default value:** `"start"`

    **Aliases:** `vertical_align_items` in a row, `horizontal_align_items` in a column

    A row's cross axis is vertical, so `"start"` aligns children to the top, while
    `"end"` aligns them to the bottom. For columns, `"start"` is on the left if
    `text_direction` is `"ltr"`, and the right if `rtl`.
    """
    justify_content: str | None = validated_property(START, CENTER, END, initial=START)
    """The alignment of this box's children along the main axis.

    **Allowed values:** `"start"`, `"center"`, or `"end"`

    **Default value:** `"start"`

    **Aliases:** `horizontal_align_content` in a row, `vertical_align_content` in a
    column

    A column's main axis is vertical, so `"start"` aligns children to the top, while
    `"end"` aligns them to the bottom. For rows, `"start"` is on the left if
    `text_direction` is `"ltr"`, and the right if `"rtl"`.

    This property only has an effect if there is some free space in the main axis. For
    example, if any children have a non-zero `flex` value, then they will consume all
    the available space, and `justify_content` will make no difference to the layout.
    """
    gap: int = validated_property(integer=True, initial=0)
    """The amount of space to allocate between adjacent children, in
    [CSS pixels][css-units].

    **Allowed values:** an integer

    **Default value:** `0`
    """
    width: str | int = validated_property(NONE, integer=True, initial=NONE)
    """A specified fixed width for the box, in [CSS pixels][css-units].

    **Allowed values:** an integer or `"none"`

    **Default value:** `"none"`

    The final width for the box may be larger, if the children of the box cannot fit
    inside the specified space.
    """
    height: str | int = validated_property(NONE, integer=True, initial=NONE)
    """A specified fixed height for the box, in [CSS pixels][css-units].

    **Allowed values:** an integer or `"none"`

    **Default value:** `"none"`

    The final height for the box may be larger, if the children of the box cannot fit
    inside the specified space.
    """
    flex: float = validated_property(number=True, initial=0)
    """A weighting that is used to compare this box with its siblings when allocating
    remaining space in a box.

    **Allowed values:** a floating-point number

    **Default value:** `0.0`

    Once fixed space allocations have been performed, this box will assume `flex /
    (sum of all flex for all siblings)` of all remaining available space in the
    direction of the parent's layout.
    """
    margin_top: int = validated_property(integer=True, initial=0)
    """"""
    margin_right: int = validated_property(integer=True, initial=0)
    """"""
    margin_bottom: int = validated_property(integer=True, initial=0)
    """"""
    margin_left: int = validated_property(integer=True, initial=0)
    """The amount of space to allocate outside the edge of the box, in
    [CSS pixels][css-units].

    **Allowed values:** an integer

    **Default value:** `0`

    """
    margin: (
        int
        | tuple[int]
        | tuple[int, int]
        | tuple[int, int, int]
        | tuple[int, int, int, int]
    ) = directional_property("margin{}")
    """A shorthand for setting the top, right, bottom and left margin with a single
    declaration.

    **Allowed values:** a tuple consisting of `(margin_top, margin_right, margin_bottom,
    margin_left)`

    **Default value:** `(0, 0, 0, 0)`

    **Accepts:** an integer or a sequence of 1–4 integers

    If 1 integer is provided, that value will be used as the margin for all sides.

    If 2 integers are provided, the first value will be used as the margin for the top
    and bottom; the second will be used as the value for the left and right.

    If 3 integers are provided, the first value will be used as the top margin, the
    second for the left and right margin, and the third for the bottom margin.

    If 4 integers are provided, they will be used as the top, right, bottom and left
    margin, respectively.
    """
    color: ColorT | str | None = validated_property(color=True)
    """The foreground color for the object being rendered.

    **Allowed values:** a [color][toga.colors.ColorT] or `None`

    **Default value:** `None`; will use the system default

    Some objects may not use the value.
    """
    background_color: ColorT | str | None = validated_property(TRANSPARENT, color=True)
    """The background color for the object being rendered.

    **Allowed values:** a [color][toga.colors.ColorT], `"transparent"`, or `None`

    **Default value:** `None`; will use the system default

    Some objects may not use the value.
    """
    text_align: str | None = validated_property(LEFT, RIGHT, CENTER, JUSTIFY)
    """The alignment of text in the object being rendered.

    **Allowed values:** `"left"`, `"right"`, `"center"`, or `"justify"`

    **Default value:** `"left"` if `text_direction` is `"ltr"`; `"right"` if
    `text_direction` is `"rtl"`

    """
    text_direction: str | None = validated_property(RTL, LTR, initial=LTR)
    """The natural direction of horizontal content.

    **Allowed values:** `"rtl"` or `"ltr"`

    **Default value:** `"rtl"`
    """
    font_family: str | list[str] = list_property(
        *SYSTEM_DEFAULT_FONTS, string=True, initial=[SYSTEM]
    )
    """A list defining possible font families, in order of preference.

    **Value**: a list of strings

    **Default value:** `["system"]`

    **Accepts:** a string or a sequence of strings

    The first item that maps to a valid font will be used. If none can be resolved, the
    system font will be used. Setting to a single string value is the same as setting
    to a list containing that string as the only item.

    A value of `"system"` indicates that whatever is a system-appropriate font should be
    used.

    A value of `"serif"`, `"sans-serif"`, `"cursive"`, `"fantasy"`, or `"monospace"`
    will use a system-defined font that matches the description (e.g. Times New Roman
    for `"serif"`, Courier New for `"monospace"`).

    Any other value will be checked against the family names previously registered with
    [`Font.register`][toga.Font.register].

    On supported platforms (currently Windows and Linux), if Toga doesn't recognize the
    family as one of its predefined builtins or as a font you've registered, it will
    attempt to load the requested font from your system before falling back to the
    default system font.
    """
    font_style: str = validated_property(*FONT_STYLES, initial=NORMAL)
    """The style of the font to be used.

    **Allowed values:** `"normal"`, `"italic"`, or `"oblique"`

    **Default value:** `"normal"`

    **Note:** Windows and Android do not support the oblique font style. A request for
    an `"oblique"` font will be interpreted as `"italic"`.
    """
    font_variant: str = validated_property(*FONT_VARIANTS, initial=NORMAL)
    """The variant of the font to be used.

    **Allowed values:** `"normal"` or `"small_caps"`

    **Default value:** `"normal"`

    **Note:** Windows and Android do not support the small caps variant. A request for a
    `"small_caps"` font will be interpreted as `"normal"`.
    """
    font_weight: str = validated_property(*FONT_WEIGHTS, initial=NORMAL)
    """The weight of the font to be used.

    **Allowed values:** `"normal"` or `"bold"`

    **Default value:** `"normal"`
    """
    font_size: int = validated_property(integer=True, initial=SYSTEM_DEFAULT_FONT_SIZE)
    """The size of the font to be used, in [CSS points][css-units].

    **Allowed values:** an integer

    **Default value:** `-1`; will use the system default size. This is also stored as a
    constant named `SYSTEM_DEFAULT_FONT_SIZE`.
    """
    font: (
        tuple[int, list[str] | str]
        | tuple[str, int, list[str] | str]
        | tuple[str, str, int, list[str] | str]
        | tuple[str, str, str, int, list[str] | str]
    ) = composite_property(
        optional=("font_style", "font_variant", "font_weight"),
        required=("font_size", "font_family"),
    )
    """A shorthand for simultaneously setting multiple properties of a font.

    **Allowed values:** a tuple consisting of `(font_style, font_variant, font_weight,
    font_size, font_family)`

    **Default value:** `("normal", "normal", "normal", -1, ["system"])`

    **Accepts:** any valid values (in order) for `font_size` and `font_family`, preceded
    by any combination and order of valid values for `font_style`, `font_variant`, and
    `font_weight`.

    Any of the three optional values (style, variant, and weight) not
    specified will be reset to `"normal"`.
    """

    ######################################################################
    # Directional aliases
    ######################################################################

    horizontal_align_content: str | None = aliased_property(
        source={Condition(direction=ROW): "justify_content"}
    )
    horizontal_align_items: str | None = aliased_property(
        source={Condition(direction=COLUMN): "align_items"}
    )
    vertical_align_content: str | None = aliased_property(
        source={Condition(direction=COLUMN): "justify_content"}
    )
    vertical_align_items: str | None = aliased_property(
        source={Condition(direction=ROW): "align_items"}
    )

    ######################################################################
    # 2024-12: Backwards compatibility for Toga < 0.5.0
    ######################################################################

    padding: (
        int
        | tuple[int]
        | tuple[int, int]
        | tuple[int, int, int]
        | tuple[int, int, int, int]
    ) = aliased_property(source="margin", deprecated=True)
    padding_top: int = aliased_property(source="margin_top", deprecated=True)
    padding_right: int = aliased_property(source="margin_right", deprecated=True)
    padding_bottom: int = aliased_property(source="margin_bottom", deprecated=True)
    padding_left: int = aliased_property(source="margin_left", deprecated=True)

    alignment: str | None = _alignment_property(TOP, RIGHT, BOTTOM, LEFT, CENTER)

display = validated_property(PACK, NONE, initial=PACK) class-attribute instance-attribute

Defines how to display the widget.

Allowed values: "pack" or "none"

Default value: "pack"

A value of "pack" will apply the pack layout algorithm to this node and its descendants. A value of "none" removes the widget from the layout entirely. Space will be allocated for the widget as if it were there, but the widget itself will not be visible.

visibility = validated_property(VISIBLE, HIDDEN, initial=VISIBLE) class-attribute instance-attribute

Defines whether the widget should be drawn.

Allowed values: "hidden" or "visible"

Default value: "visible"

A value of "visible" means the widget will be displayed. A value of "hidden" removes the widget from view, but allocates space for the widget as if it were still in the layout.

Any children of a hidden widget are implicitly removed from view.

If a previously hidden widget is made visible, any children of the widget with a visibility of "hidden" will remain hidden. Any descendants of the hidden child will also remain hidden, regardless of their visibility.

direction = validated_property(ROW, COLUMN, initial=ROW) class-attribute instance-attribute

The packing direction for children of the box.

Allowed values: "row" or "column"

Default value: "row"

A value of "column" indicates children will be stacked vertically, from top to bottom. A value of "row" indicates children will be packed horizontally; left-to-right if text_direction is "ltr", or right-to-left if text_direction is "rtl".

align_items = validated_property(START, CENTER, END) class-attribute instance-attribute

The alignment of this box's children along the cross axis.

Allowed values: "start", "center", or "end"

Default value: "start"

Aliases: vertical_align_items in a row, horizontal_align_items in a column

A row's cross axis is vertical, so "start" aligns children to the top, while "end" aligns them to the bottom. For columns, "start" is on the left if text_direction is "ltr", and the right if rtl.

justify_content = validated_property(START, CENTER, END, initial=START) class-attribute instance-attribute

The alignment of this box's children along the main axis.

Allowed values: "start", "center", or "end"

Default value: "start"

Aliases: horizontal_align_content in a row, vertical_align_content in a column

A column's main axis is vertical, so "start" aligns children to the top, while "end" aligns them to the bottom. For rows, "start" is on the left if text_direction is "ltr", and the right if "rtl".

This property only has an effect if there is some free space in the main axis. For example, if any children have a non-zero flex value, then they will consume all the available space, and justify_content will make no difference to the layout.

gap = validated_property(integer=True, initial=0) class-attribute instance-attribute

The amount of space to allocate between adjacent children, in CSS pixels.

Allowed values: an integer

Default value: 0

width = validated_property(NONE, integer=True, initial=NONE) class-attribute instance-attribute

A specified fixed width for the box, in CSS pixels.

Allowed values: an integer or "none"

Default value: "none"

The final width for the box may be larger, if the children of the box cannot fit inside the specified space.

height = validated_property(NONE, integer=True, initial=NONE) class-attribute instance-attribute

A specified fixed height for the box, in CSS pixels.

Allowed values: an integer or "none"

Default value: "none"

The final height for the box may be larger, if the children of the box cannot fit inside the specified space.

flex = validated_property(number=True, initial=0) class-attribute instance-attribute

A weighting that is used to compare this box with its siblings when allocating remaining space in a box.

Allowed values: a floating-point number

Default value: 0.0

Once fixed space allocations have been performed, this box will assume flex / (sum of all flex for all siblings) of all remaining available space in the direction of the parent's layout.

margin_top = validated_property(integer=True, initial=0) class-attribute instance-attribute

margin_right = validated_property(integer=True, initial=0) class-attribute instance-attribute

margin_bottom = validated_property(integer=True, initial=0) class-attribute instance-attribute

margin_left = validated_property(integer=True, initial=0) class-attribute instance-attribute

The amount of space to allocate outside the edge of the box, in CSS pixels.

Allowed values: an integer

Default value: 0

margin = directional_property('margin{}') class-attribute instance-attribute

A shorthand for setting the top, right, bottom and left margin with a single declaration.

Allowed values: a tuple consisting of (margin_top, margin_right, margin_bottom, margin_left)

Default value: (0, 0, 0, 0)

Accepts: an integer or a sequence of 1–4 integers

If 1 integer is provided, that value will be used as the margin for all sides.

If 2 integers are provided, the first value will be used as the margin for the top and bottom; the second will be used as the value for the left and right.

If 3 integers are provided, the first value will be used as the top margin, the second for the left and right margin, and the third for the bottom margin.

If 4 integers are provided, they will be used as the top, right, bottom and left margin, respectively.

color = validated_property(color=True) class-attribute instance-attribute

The foreground color for the object being rendered.

Allowed values: a [color][toga.colors.ColorT] or None

Default value: None; will use the system default

Some objects may not use the value.

background_color = validated_property(TRANSPARENT, color=True) class-attribute instance-attribute

The background color for the object being rendered.

Allowed values: a [color][toga.colors.ColorT], "transparent", or None

Default value: None; will use the system default

Some objects may not use the value.

text_align = validated_property(LEFT, RIGHT, CENTER, JUSTIFY) class-attribute instance-attribute

The alignment of text in the object being rendered.

Allowed values: "left", "right", "center", or "justify"

Default value: "left" if text_direction is "ltr"; "right" if text_direction is "rtl"

text_direction = validated_property(RTL, LTR, initial=LTR) class-attribute instance-attribute

The natural direction of horizontal content.

Allowed values: "rtl" or "ltr"

Default value: "rtl"

font_family = list_property(*SYSTEM_DEFAULT_FONTS, string=True, initial=[SYSTEM]) class-attribute instance-attribute

A list defining possible font families, in order of preference.

Value: a list of strings

Default value: ["system"]

Accepts: a string or a sequence of strings

The first item that maps to a valid font will be used. If none can be resolved, the system font will be used. Setting to a single string value is the same as setting to a list containing that string as the only item.

A value of "system" indicates that whatever is a system-appropriate font should be used.

A value of "serif", "sans-serif", "cursive", "fantasy", or "monospace" will use a system-defined font that matches the description (e.g. Times New Roman for "serif", Courier New for "monospace").

Any other value will be checked against the family names previously registered with Font.register.

On supported platforms (currently Windows and Linux), if Toga doesn't recognize the family as one of its predefined builtins or as a font you've registered, it will attempt to load the requested font from your system before falling back to the default system font.

font_style = validated_property(*FONT_STYLES, initial=NORMAL) class-attribute instance-attribute

The style of the font to be used.

Allowed values: "normal", "italic", or "oblique"

Default value: "normal"

Note: Windows and Android do not support the oblique font style. A request for an "oblique" font will be interpreted as "italic".

font_variant = validated_property(*FONT_VARIANTS, initial=NORMAL) class-attribute instance-attribute

The variant of the font to be used.

Allowed values: "normal" or "small_caps"

Default value: "normal"

Note: Windows and Android do not support the small caps variant. A request for a "small_caps" font will be interpreted as "normal".

font_weight = validated_property(*FONT_WEIGHTS, initial=NORMAL) class-attribute instance-attribute

The weight of the font to be used.

Allowed values: "normal" or "bold"

Default value: "normal"

font_size = validated_property(integer=True, initial=SYSTEM_DEFAULT_FONT_SIZE) class-attribute instance-attribute

The size of the font to be used, in CSS points.

Allowed values: an integer

Default value: -1; will use the system default size. This is also stored as a constant named SYSTEM_DEFAULT_FONT_SIZE.

font = composite_property(optional=('font_style', 'font_variant', 'font_weight'), required=('font_size', 'font_family')) class-attribute instance-attribute

A shorthand for simultaneously setting multiple properties of a font.

Allowed values: a tuple consisting of (font_style, font_variant, font_weight, font_size, font_family)

Default value: ("normal", "normal", "normal", -1, ["system"])

Accepts: any valid values (in order) for font_size and font_family, preceded by any combination and order of valid values for font_style, font_variant, and font_weight.

Any of the three optional values (style, variant, and weight) not specified will be reset to "normal".

The relationship between Pack and CSS

Pack aims to be a functional subset of CSS. Any Pack layout can be converted into an equivalent CSS layout. After applying this conversion, the CSS layout should be considered a "reference implementation". Any disagreement between the rendering of a converted Pack layout in a browser, and the layout produced by the Toga implementation of Pack should be considered to be either a bug in Toga, or a bug in the mapping.

The mapping that can be used to establish the reference implementation is:

  • The reference HTML layout document is rendered in no-quirks mode, with a default CSS stylesheet:
<!DOCTYPE html>
<html>
    <head>
       <meta charset="UTF-8" />
       <title>Pack layout testbed</title>
       <style>
          html, body {
             height: 100%;
          }
          body {
             overflow: hidden;
             display: flex;
             margin: 0;
             white-space: pre;
          }
          div {
             display: flex;
             white-space: pre;
          }
       </style>
    </head>
    <body></body>
</html>
  • The root widget of the Pack layout can be mapped to the <body> element of the HTML reference document. The rendering area of the browser window becomes the view area that Pack will fill.

  • ImageViews map to <img> elements. The <img> element has an additional style of object-fit: contain unless both height and width are defined.

  • All other widgets are mapped to <div> elements.

  • The following Pack declarations can be mapped to equivalent CSS declarations:

Pack property CSS property
direction: <str> flex-direction: <str>
display: pack display: flex
flex: <int> If direction == "row" and width is set, or direction == "column" and height is set, ignore. Otherwise, flex: <int> 0 auto.
font_size: <int> font-size: <int>pt
height: <value> height: <value>px if value is an integer; height: auto if value is "none".
margin_top: <int> margin-top: <int>px
margin_bottom: <int> margin-bottom: <int>px
margin_left: <int> margin-left: <int>px
margin_right: <int> margin-right: <int>px
text_direction: <str> direction: <str>
width: <value> width: <value>px if value is an integer; width: auto if value is "none".
  • All other Pack declarations should be used as-is as CSS declarations, with underscores being converted to dashes (e.g., background_color becomes background-color).