3. Qt Widgets Layouts

When you create a Qt widget like QPushButton you need a way to position it inside a window or a container widget. It is possible, but quite uncommon, to position Qt widgets by setting their x and y coordinates. Most Qt applications use layouts to automatically arrange widgets in rows, columns grids, forms, or stacks, to achieve the desired layout. In contrast to manual positioning, layouts automatically position and resize widgets when the available space changes, ensuring that they are consistently aranged and remain usable.[^1]

This chapter introduces the four layout classes you will reach for in almost every Qt application you build. All of them inherit from the QLayout class:

QLayout provides a shared api for adding and removing widgets, and configuring layouts:

Method Description
addWidget() Adds a widget to the layout
addLayout() Nests a child layout inside this one
removeWidget() Removes a widget, leaving it alive but hidden
setContentsMargins() Sets the padding between the layout edge and its widgets
setSpacing() Sets the gap between widgets

QBoxLayout also adds these methods:

Method Description
addStretch() Inserts flexible empty space that expands to fill available room
addSpacing() Inserts a fixed-size gap in pixels
setDirection() Reverses flow direction without switching layout class

addSpacing() adds a fixed number of pixels, while addStretch() grows and shrinks as the layout resizes.

3.1 Stacking Widgets Vertically - QVBoxLayout

QVBoxLayout arranges widgets in a column. If you look up the QVBoxLayout parents in the Qt documentation, you’ll notice that QWidget is not a QVBoxLayout’s parent class. This means you cannot show a layout on the screen - it is not a visual element but a geometry manager.

An icon of a clipboard-list1

Your task is to bulid an account summary panel - a column of account push buttons (Checking, Savings, Credit Card) stacked top to bottom, with empty space below the last entry, and a label showing account details.

To add a vertical layout to a QWidget:

 1 # Demonstrate how to stack widgets vertically
 2 # using QVBoxLayout.
 3 
 4 import sys
 5 from PySide6.QtCore import Slot
 6 from PySide6.QtWidgets import (QApplication,
 7     QWidget, QPushButton, QLabel, QVBoxLayout)
 8 
 9 
10 class Window(QWidget):
11 
12     def __init__(self):
13 
14         super().__init__()
15 
16         # 1. Create the layout object
17         #    and set it as the window layout.
18 
19         layout = QVBoxLayout()
20         self.setLayout(layout)
21 
22         # 2. Create widgets.
23 
24         checking_button = QPushButton('Checking Account')
25         savings_button = QPushButton('Savings Account')
26         credit_card_button = QPushButton('Credit Card Account')
27 
28         # 3. Add the widgets to the layout.
29 
30         layout.addWidget(checking_button)
31         layout.addWidget(savings_button)
32         layout.addWidget(credit_card_button)
33         
34         layout.addStretch()
35         
36         self.details_label = QLabel()
37         layout.addWidget(self.details_label)
38         
39         checking_button.clicked.connect(self.show_checking)
40         savings_button.clicked.connect(self.show_savings)
41         credit_card_button.clicked.connect(self.show_credit_card)
42         
43     @Slot()
44     def show_checking(self):
45         self.details_label.setText('Everyday spending')
46         
47     @Slot()
48     def show_savings(self):
49         self.details_label.setText('Emergency fund')
50         
51     @Slot()
52     def show_credit_card(self):
53         self.details_label.setText('Monthly expenses')
54 
55 
56 if __name__ == '__main__':
57 
58     app = QApplication(sys.argv)
59     main_window = Window()
60     main_window.show()
61     sys.exit(app.exec())
  1. Create a QVBoxLayout instance and set it as the main window layout with setLayout(). This also works for any QWidget subclass. You can nest layouts inside other layouts to achieve complex layouts.

  2. Create widgets you want to lay out. Here we create three QPushButton instances.

  3. Add the widgets to the layout using addWidget(). For nested layouts you would use addLayout() instead.

If you run the script and resize the window, the buttons stack vertically and expand horizontally to fill the width, but their height stays fixed, creating gaps between them. There are two ways you can handle this:

  • Add stretchable space with addStretch() at the end of the layout - it pushes the widgets to the top while allowing empty space to grow This is what we do in our example.
  • Set a widget’s size Expanding for both directions, e.g. button_1.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding). This lets it resize in both axes.

We’ve also connected each button’s clicked signals to a slot that updates the label text to the account details when its button is clicked.

When executed, the script shows a window like this:

3.2 Placing Widgets Side by Side - QHBoxLayout

An icon of a clipboard-list1

You are tasked with building a single account row - an account name button on the left and a view label on the right, pushed to opposite ends of the row.

The use QVBoxLayout in your application:

 1 # QHBoxLayout is the horizontal box layout.
 2 
 3 import sys
 4 from PySide6.QtCore import Slot
 5 from PySide6.QtWidgets import (QApplication, 
 6     QWidget, QPushButton, QLabel, QHBoxLayout)
 7 
 8 
 9 class Window(QWidget):
10     
11     def __init__(self):
12 
13         super().__init__()
14         
15         # 1. Create the layout 
16         #    and set it as the window layout
17         
18         layout = QHBoxLayout()
19         self.setLayout(layout)
20         
21         # 2. Create the widgets
22 
23         button = QPushButton('Savings account')
24         self.label = QLabel('')
25         self.label.setMinimumWidth(100)
26 
27         # 3 - Add widgets to the layout
28         
29         layout.addWidget(button)
30         layout.addStretch()
31         layout.addWidget(self.label)
32 
33         button.clicked.connect(self.show_account_details)
34         
35     @Slot()
36     def show_account_details(self):
37         self.label.setText('Emergency Fund')
38 
39 
40 if __name__ == '__main__':
41 
42     app = QApplication(sys.argv)
43     main_window = Window()
44     main_window.show()
45     sys.exit(app.exec())
  1. Create the layout and assign it to a QWidget with setLayout()

  2. Create child widgets. Here, a QPushButton and QLabel. We set the label’s minimum width to fit its text.

  3. Add them to the layout using addWidget() in the desired order.

We also connect the button’s clicked signal to update the label with the account details.

After execution, you’ll see a window like this:

Both layout classes arrange widgets in the order you add them with addWidget(), QVBoxLayout top to bottom, and QHBoxLayout left to right. You can change this via their setDirection() method with the QBoxLayout.BottomToTop or QBoxLayout.RightToLeft. You can achieve dynamic layouts by using removeWidget() to remove a widget from a layout or insertWidget() to insert a widget out of order.

If you create a widget without setting its parent (button = QPushButton('Savings Acount') instead of button = QPushButton('Savings Acount', self)), as we do in the above examples, adding them to a widget’s layout reparents them to that widget.

Note that the buttons in both examples are local variables. Once added to a layout, Qt’s parent-child ownership system keeps them alive even after __init__() returns. The labels, on the other hand, are instance fields (self.label) because they need to be accessed by slot methods after initialization.

3.3 Nesting Layouts

Nesting layouts within one another allows you to build complex and flexible user interfaces. It is universally used in real-world Qt Widgets applications.

An icon of a clipboard-list1

Your task is to build the account list panel where each row shows a selectable account button on the left and its current balance balance on the right. Clicking an account updates a details label at the bottom of the panel.

To use nested Qt layouts in your application:

 1 import sys
 2 from PySide6.QtCore import Qt
 3 from PySide6.QtWidgets import (QApplication, QWidget,
 4     QPushButton, QLabel, QVBoxLayout, QHBoxLayout)
 5 
 6 
 7 ACCOUNTS = [
 8     ("Checking Account", 1240.00),
 9     ("Savings Account", 8500.00),
10     ("Credit Card", 340.00)
11 ]
12 
13 
14 class Window(QWidget):
15 
16     def __init__(self):
17 
18         super().__init__()
19         self.setFixedSize(240, 180)
20         
21         # 1. Create the parent layout.
22 
23         layout = QVBoxLayout()
24         self.setLayout(layout)
25         
26         for name, balance in ACCOUNTS:
27             
28             # 2. Create child layouts.
29             
30             row_layout = QHBoxLayout()
31 
32             account_button = QPushButton(name)
33             account_button.setMinimumWidth(120)
34             account_button.clicked.connect(
35                 lambda checked, name=name, balance=balance:
36                     self.show_account_details(name, balance))
37 
38             balance_label = QLabel(f'${balance:.2f}')
39             balance_label.setAlignment(Qt.AlignmentFlag.AlignRight)
40             balance_label.setMinimumWidth(80)
41 
42             row_layout.addWidget(account_button)
43             row_layout.addStretch()
44             row_layout.addWidget(balance_label)
45             
46             # 3. Add child layouts to the parent layout.
47             
48             layout.addLayout(row_layout)
49         
50         layout.addStretch()
51         
52         self.details_label = QLabel()
53         self.details_label.setAlignment(Qt.AlignmentFlag.AlignHCenter)
54         layout.addWidget(self.details_label)
55         
56     def show_account_details(self, name, balance):
57         self.details_label.setText(
58             f'Selected: {name}  ${balance:.2f}')
59 
60 
61 if __name__ == '__main__':
62 
63     app = QApplication(sys.argv)
64     main_window = Window()
65     main_window.show()
66     sys.exit(app.exec())
  1. Create the outer layout. Create a QVBoxLayout and set it as the window layout using setLayout().

  2. Create a row layout for each account. The account data lives in the ACCOUNTS list. Iterate over it and for each entry create a QHBoxLayout. Add a QPushButton for the account name and a QLabelfor the balance. Connect each button’s clicked signal to show_account_details() which updates the details label with the selected account’s name and balance.

  3. Add each row layout to the outer layout. Use addLayout() rather than addWidget().

When you run the application and click one of the account buttons, its details appear in the label at the bottom of the panel:

3.4 Arranging Widgets in a Grid - QGridLayout

QGridLayout positions widgets in a grid. Unlike the box layouts, you specify each widget’s row and column explicitly when adding it and the layout handles all sizing and spacing automatically.

An icon of a clipboard-list1

Your task is to build an entry pad for logging transaction amounts. The pad is a 3x4 grid of digit buttons where * clears the display and # confirms the entry.

To use a grid layout in your application:

 1 import sys
 2 
 3 from PySide6.QtCore import Qt
 4 from PySide6.QtWidgets import (QApplication, QWidget,
 5     QPushButton, QLabel, QVBoxLayout, QGridLayout)
 6 
 7 
 8 KEYS = [
 9     ('1', 0, 0), ('2', 0, 1), ('3', 0, 2),
10     ('4', 1, 0), ('5', 1, 1), ('6', 1, 2),
11     ('7', 2, 0), ('8', 2, 1), ('9', 2, 2),
12     ('*', 3, 0), ('0', 3, 1), ('#', 3, 2)
13 ]
14 
15 
16 class Window(QWidget):
17     
18     def __init__(self):
19 
20         super().__init__()
21         
22         layout = QVBoxLayout()
23         self.setLayout(layout)
24         
25         self.display_label = QLabel()
26         font = self.display_label.font()
27         font.setPointSize(16)
28         font.setBold(True)
29         self.display_label.setFont(font)
30         self.display_label.setAlignment(Qt.AlignmentFlag.AlignRight)
31         layout.addWidget(self.display_label)
32         
33         # 1. Create a QGridLayout`.
34         
35         keypad_layout = QGridLayout()
36         
37         for key, row, col in KEYS:
38             
39             # 2. Create the buttons.
40             
41             button = QPushButton(key)
42             button.setFixedSize(60, 40)
43             
44             button.clicked.connect(
45                 lambda checked, key=key: 
46                     self.update_display(key))
47             
48             # 3. Add each button to the grid.
49             
50             keypad_layout.addWidget(button, row, col)
51             
52         layout.addLayout(keypad_layout)
53         
54     def update_display(self, key):
55         if key == '*':
56             self.display_label.clear()
57         elif key == '#':
58             self.display_label.setText('DONE')
59         else:
60             text = self.display_label.text()
61             if text == 'DONE':
62                 text = ''
63             self.display_label.setText(text + key)
64 
65 
66 if __name__ == '__main__':
67 
68     app = QApplication(sys.argv)
69     main_window = Window()
70     main_window.show()
71     sys.exit(app.exec())
  1. Create the layouts. Since the pad needs a display label above the grid, the window layout is a QVBoxLayout. A QGridLayout is created separately and added to it with addLayout().

  2. Create the buttons. The KEYS list holds each key’s label and its row and column position in the grid. Iterate over it and create a QPushButton for each entry. All buttons are given uniform fized size, and each clicked signal is connected to update_display() via a lambda that captures the key label.

  3. Add each button to the grid. QGridLayout has two addWidget() overloads:

Method Description
addWidget(widget, row, column) Adds a widget at the specified grid position
addWidget(widget, fromRow, fromColumn, rowSpan, columnSpan) Adds a widget spanning multiple rows and/or columns

The example uses the first overload in which each button occupies exactly one cell. The row and column come directly from the KEYS data so no manual coordinate tracking is needed.

In update_display(), pressing a digit appends it to the display label. * clears the display, and # sets it to DONE.

After execution, the window looks like this:

3.5 Laying Out Forms - QFormLayout

QFormLayout arranges widgets in a two-column rows with a label on the left and an entry field on the right. It can be used for data entry forms where fields need a descriptive label to their left side. You can conveniently add both the label and the field in a single step using one of the addRow() overloads:

Method Description
addRow(labelText, widget) Adds a label string and a field widget as a new row
addRow(labelText, layout) Adds a label string and a nested layout as a new row
addRow(widget) Adds a full-width widget spanning both columns

We use the first overload in the example. The second lets you add an inner layout instead of a field (an edit field with a button beside it for example). The third can be used to add separators and section headers inside a form.

An icon of a clipboard-list1

You need to build a transaction entry form for logging payments. The form has three fields, Amount, Category and Description, with a summary label that updates as the user fills them in.

To use a form layout in your application:

 1 import sys
 2 from PySide6.QtCore import Slot
 3 from PySide6.QtWidgets import (QApplication, QWidget,
 4     QFormLayout, QVBoxLayout, QLineEdit, QLabel)
 5 
 6 
 7 class Window(QWidget):
 8 
 9     def __init__(self):
10 
11         super().__init__()
12 
13         layout = QVBoxLayout()
14         self.setLayout(layout)
15 
16         form_layout = QFormLayout()
17 
18         self.fields = {
19             'Amount': QLineEdit(),
20             'Recipient': QLineEdit(),
21             'Note': QLineEdit(),
22         }
23 
24         for label, edit in self.fields.items():
25             form_layout.addRow(label, edit)
26             edit.editingFinished.connect(self.update_summary)
27 
28         self.summary_label = QLabel()
29         self.summary_label.setFixedHeight(60)
30 
31         layout.addLayout(form_layout)
32         layout.addStretch()
33         layout.addWidget(self.summary_label)
34 
35     @Slot()
36     def update_summary(self):
37         self.summary_label.setText('\n'.join(
38             f'{label}:\t{edit.text()}'
39             for label, edit in self.fields.items()))
40 
41 
42 if __name__ == '__main__':
43 
44     app = QApplication(sys.argv)
45     main_window = Window()
46     main_window.show()
47     sys.exit(app.exec())
  1. Create the layouts. The window layout is a QVBoxLayout holding the form and the summary label. The form is a QFormLayout added to the outer layout with addLayout().

  2. Create the fields. The fields are stored in a dict mapping each label name to a QLineEdit, a simple one-line text editor. Iterate over the dict and add each item to the form layout using addRow(label, field).

  3. Connect the fields. Each field’s editingFinished signal is connected to update_summary() inside the loop. The slot reads all three fields from the dict and updates the summary label below the form.

In this chapter you have seen how Qt’s layout managers, QVBoxLayout, QHBoxLayout, QGridLayout, and QFormLayout, handle widget positionong and resizing automatically. You have also seen how layouts can be nested to build complex user interfaces, and how data-driven widget creation with loops keeps the repetitive code clean and maintainable.

The next several chapters focus on widgets themselves, starting with display widgets and buttons, then moving through various input widgets and beyond.